diff options
Diffstat (limited to 'tree-sitter/dsk/dsk-cli')
29 files changed, 3026 insertions, 0 deletions
diff --git a/tree-sitter/dsk/dsk-cli/.gitignore b/tree-sitter/dsk/dsk-cli/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/tree-sitter/dsk/dsk-cli/README.md b/tree-sitter/dsk/dsk-cli/README.md new file mode 100644 index 0000000..d07a9dd --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/README.md @@ -0,0 +1,126 @@ +# dsk-cli + +DSL Development Kit (DSK) – a CLI that helps you design, build, test, and ship Tree-sitter based Domain-Specific Languages (DSLs) with a convention-over-configuration workflow. + +## Why this is helpful +- **Lower barrier to entry**: Generate a working Tree-sitter `grammar.js` from examples via an interactive flow. +- **No boilerplate**: One command builds both a C static library and a Node.js addon package. +- **Fast iteration**: Built-in watch mode (`dsk dev`) and testing (`dsk test`). +- **Editor-ready**: Generate Tree-sitter highlight queries and editor scaffolds (Neovim, Emacs, VS Code). +- **Consistent outputs**: Standardized project layout and generated artifacts under `generated/` and `dist/`. + +## Prerequisites +- macOS or Linux +- [Bun](https://bun.sh) (to run `dsk`) +- [tree-sitter CLI](https://tree-sitter.github.io/tree-sitter/creating-parsers#installation) (`npm i -g tree-sitter-cli` or `brew install tree-sitter`) +- C toolchain: clang/gcc and `ar` (macOS: `xcode-select --install`, Ubuntu: `build-essential`) +- For the generated JS addon builds: Python 3 and make are typically required by node-gyp (the template depends on `node-gyp`) + +## Install (for developing this CLI) +```bash +bun install +bunx tsc +bun link # optional: exposes `dsk` on your PATH during development +``` + +## Quickstart (creating a DSL) +```bash +# 1) Scaffold a new DSL project interactively +dsk new my-lang --interactive + +# 2) Move into the project directory +cd my-lang + +# 3) Build (generates parser, C lib, and JS addon) +dsk build + +# 4) Run tests (expects corpus in `corpus/` or queries) +dsk test -v + +# 5) Iterate with watch mode +dsk dev --debounce 150 + +# 6) Generate editor highlighting scaffolds +dsk highlight + +# 7) Package outputs for distribution +dsk package +``` + +## Command reference + +### `dsk new <name> [--interactive]` +Creates a new DSL project. With `--interactive`, it asks for examples and infers common token patterns, then generates a starter `grammar.js` and project structure using templates. + +### `dsk build` [--verbose] [--skip-c] [--skip-js] +Builds your DSL: +- Runs `tree-sitter generate` +- Builds a C static library and header under `generated/c/` +- Creates a Node.js addon package under `generated/js/` (compiles via node-gyp) + +Outputs: +- `generated/c/include/<dsl>.h` +- `generated/c/lib/lib<dsl>.a` +- `generated/js/` (package.json, binding.gyp, compiled addon, copied `src/`) + +### `dsk test` [-u|--update] [-f|--filter <regex>] [--cwd <dir>] [-v|--verbose] [patterns...] +Thin wrapper around `tree-sitter test` that streams output directly. +- `--update`: updates expected output snapshots +- `--filter <regex>`: only run tests whose description matches +- `--cwd <dir>`: run tests in a different directory +- `--verbose`: pass through verbose output + +Examples: +```bash +dsk test +dsk test -v -f numbers +dsk test -u corpus/examples.txt +``` + +### `dsk dev` [--debounce <ms>] [--quiet] [-v|--verbose] +Watches `grammar.js` and on changes runs `dsk build` then `dsk test`. +- `--debounce <ms>`: delay rebuild after changes (default: 150) +- `--quiet`: suppress non-error logs +- `--verbose`: pass to `dsk build` (more detailed build logs) + +### `dsk highlight` +Generates highlighting assets and editor scaffolds: +- `generated/editors/tree-sitter/highlights.scm` +- `generated/editors/neovim/setup-instructions.md` +- `generated/editors/emacs/<lang>-mode.el` +- `generated/editors/vscode/syntaxes/<lang>.tmLanguage.json` +- `generated/editors/vscode/language-configuration.json` + +### `dsk package` +Packages build outputs into `dist/`: +- Archives `generated/c/` as `dist/c-artifacts.tar.gz` +- Packs the JS addon under `generated/js/` via `bun pack` (falls back to `npm pack`) + +### `dsk self:package` +Builds and packages the `dsk` CLI itself into a tarball under `release/`. +```bash +dsk self:package +# → release/dsk-cli-<version>.tgz +``` + +## Project structure (generated DSL project) +- `grammar.js`: Tree-sitter grammar +- `src/`: generated parser sources after `tree-sitter generate` +- `corpus/`: test cases for `tree-sitter test` +- `generated/`: build outputs (C and JS targets) +- `generated/editors/`: highlight queries and editor scaffolds +- `dist/`: packaged artifacts + +## Notes about the JS addon template +- Depends on `node-addon-api` and `node-gyp` (declared in template `package.json`) +- `binding.gyp` includes Node-API headers and defines `NAPI_VERSION=8` +- Builds via `npm install` or `bun install` in `generated/js/` (auto-detected) + +## Troubleshooting +- `tree-sitter` not found: install with `npm i -g tree-sitter-cli` or `brew install tree-sitter` +- C compiler not found: macOS `xcode-select --install`; Linux install `build-essential` +- node-gyp build issues: ensure Python 3 and make are available; re-run `dsk build -v` + +--- + +This project was created using `bun init` in bun v1.1.29. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/tree-sitter/dsk/dsk-cli/bun.lockb b/tree-sitter/dsk/dsk-cli/bun.lockb new file mode 100755 index 0000000..31f4569 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/bun.lockb Binary files differdiff --git a/tree-sitter/dsk/dsk-cli/package.json b/tree-sitter/dsk/dsk-cli/package.json new file mode 100644 index 0000000..ef6f9fd --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/package.json @@ -0,0 +1,33 @@ +{ + "name": "dsk-cli", + "version": "0.1.0", + "description": "DSL Development Kit - Command-line tool for creating Domain-Specific Languages", + "main": "dist/index.js", + "module": "index.ts", + "type": "module", + "bin": { + "dsk": "./dist/index.js" + }, + "scripts": { + "build": "bunx tsc", + "dev": "bun run --watch src/index.ts", + "start": "bun run dist/index.js" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/fs-extra": "^11.0.4", + "@types/inquirer": "^9.0.9", + "@types/node": "^24.2.1" + }, + "peerDependencies": { + "typescript": "^5.9.2" + }, + "dependencies": { + "chalk": "^5.5.0", + "chokidar": "^4.0.3", + "commander": "^14.0.0", + "execa": "^9.6.0", + "fs-extra": "^11.3.1", + "inquirer": "^12.9.1" + } +} \ No newline at end of file diff --git a/tree-sitter/dsk/dsk-cli/src/commands/build.ts b/tree-sitter/dsk/dsk-cli/src/commands/build.ts new file mode 100644 index 0000000..811788f --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/commands/build.ts @@ -0,0 +1,429 @@ +/** + * Build Command - Core Build Process + * + * Compiles grammar.js into C static library and JavaScript package + */ + +import { Command } from 'commander'; +import { execa } from 'execa'; +import chalk from 'chalk'; +import { existsSync, mkdirSync, writeFileSync, copyFileSync, readFileSync, cpSync } from 'fs'; +import { join, dirname, basename } from 'path'; +import { fileURLToPath } from 'url'; + +/** + * Create the build command + */ +export function createBuildCommand(): Command { + const buildCommand = new Command('build'); + + buildCommand + .description('Build the DSL parser and packages') + .option('-v, --verbose', 'Show detailed build output') + .option('--skip-c', 'Skip C library generation') + .option('--skip-js', 'Skip JavaScript package generation') + .action(async (options) => { + console.log(chalk.blue('🏗️ Building DSL parser and packages...')); + console.log(); + + try { + await runBuildProcess(options); + + console.log(); + console.log(chalk.green('✅ Build completed successfully!')); + console.log(); + console.log(chalk.blue('Generated artifacts:')); + console.log(` ${chalk.gray('•')} C library: generated/c/lib/`); + console.log(` ${chalk.gray('•')} C headers: generated/c/include/`); + console.log(` ${chalk.gray('•')} JS package: generated/js/`); + + } catch (error) { + console.error(chalk.red('❌ Build failed:'), error instanceof Error ? error.message : error); + process.exit(1); + } + }); + + return buildCommand; +} + +/** + * Run the complete build process + */ +async function runBuildProcess(options: { verbose?: boolean; skipC?: boolean; skipJs?: boolean }): Promise<void> { + // Verify we're in a DSL project directory + if (!existsSync('grammar.js')) { + throw new Error('No grammar.js found. Are you in a DSL project directory?'); + } + + // Ensure CommonJS semantics locally so tree-sitter can load grammar.js (uses module.exports) + ensureCommonJsPackageJson(); + + // Step 1: Generate parser with Tree-sitter + console.log(chalk.blue('1️⃣ Generating parser with Tree-sitter...')); + await generateParser(options.verbose); + console.log(chalk.green(' ✅ Parser generated')); + + // Step 2: Build C library (unless skipped) + if (!options.skipC) { + console.log(chalk.blue('2️⃣ Building C static library...')); + await buildCLibrary(options.verbose); + console.log(chalk.green(' ✅ C library built')); + } else { + console.log(chalk.yellow(' ⏭️ Skipping C library build')); + } + + // Step 3: Build JavaScript package (unless skipped) + if (!options.skipJs) { + console.log(chalk.blue('3️⃣ Building JavaScript package...')); + await buildJavaScriptPackage(options.verbose); + console.log(chalk.green(' ✅ JavaScript package built')); + } else { + console.log(chalk.yellow(' ⏭️ Skipping JavaScript package build')); + } +} + +/** + * Step 1: Generate parser using tree-sitter generate + */ +async function generateParser(verbose?: boolean): Promise<void> { + try { + // Check if tree-sitter CLI is available + await checkTreeSitterAvailable(); + + // Run tree-sitter generate + const result = await execa('tree-sitter', ['generate'], { + stdio: verbose ? 'inherit' : 'pipe' + }); + + if (!verbose && result.stdout) { + console.log(chalk.gray(` ${result.stdout.split('\n').slice(-2, -1)[0] || 'Generated successfully'}`)); + } + + } catch (error: any) { + if (error.command) { + throw new Error(`Tree-sitter generation failed: ${error.message}`); + } + throw error; + } +} + +/** + * Step 2: Build C static library + */ +async function buildCLibrary(verbose?: boolean): Promise<void> { + // Create output directories + const libDir = 'generated/c/lib'; + const includeDir = 'generated/c/include'; + + mkdirSync(libDir, { recursive: true }); + mkdirSync(includeDir, { recursive: true }); + + // Get project name from grammar.js or directory + const projectName = getProjectName(); + + // Compile parser.c to object file + const objectFile = join(libDir, `${projectName}.o`); + const libraryFile = join(libDir, `lib${projectName}.a`); + + try { + // Detect C compiler + const compiler = await detectCCompiler(); + console.log(chalk.gray(` Using compiler: ${compiler}`)); + + // Compile to object file + const compileArgs = [ + '-c', // Compile only, don't link + '-fPIC', // Position independent code + '-O2', // Optimize + 'src/parser.c', // Input file + '-o', objectFile // Output file + ]; + + await execa(compiler, compileArgs, { + stdio: verbose ? 'inherit' : 'pipe' + }); + + // Create static library with ar + const arArgs = [ + 'rcs', // Create archive, insert files, write symbol table + libraryFile, // Output library + objectFile // Input object file + ]; + + await execa('ar', arArgs, { + stdio: verbose ? 'inherit' : 'pipe' + }); + + // Generate header file + await generateHeaderFile(projectName, includeDir); + + console.log(chalk.gray(` Library: ${libraryFile}`)); + console.log(chalk.gray(` Header: ${join(includeDir, projectName + '.h')}`)); + + } catch (error: any) { + throw new Error(`C library build failed: ${error.message}`); + } +} + +/** + * Step 3: Build JavaScript package + */ +async function buildJavaScriptPackage(verbose?: boolean): Promise<void> { + const jsDir = 'generated/js'; + + // Create JS package directory + mkdirSync(jsDir, { recursive: true }); + + // Copy JS addon templates + await copyJSAddonTemplates(jsDir); + + // Copy src directory from tree-sitter generation + await copySourceFiles(jsDir); + + // Update binding.gyp based on available files + await updateBindingGyp(jsDir); + + // Update package.json with project name + await updateJSPackageJson(jsDir); + + // Detect runtime and install dependencies + const runtime = await detectRuntime(); + console.log(chalk.gray(` Using runtime: ${runtime}`)); + + try { + // Install dependencies and build native addon + if (runtime === 'bun') { + await execa('bun', ['install'], { + cwd: jsDir, + stdio: verbose ? 'inherit' : 'pipe' + }); + } else { + await execa('npm', ['install'], { + cwd: jsDir, + stdio: verbose ? 'inherit' : 'pipe' + }); + } + + console.log(chalk.gray(` Package: ${jsDir}/`)); + + } catch (error: any) { + throw new Error(`JavaScript package build failed: ${error.message}`); + } +} + +/** + * Check if tree-sitter CLI is available + */ +async function checkTreeSitterAvailable(): Promise<void> { + try { + await execa('tree-sitter', ['--version'], { stdio: 'pipe' }); + } catch (error) { + throw new Error( + 'tree-sitter CLI not found. Please install it:\n' + + ' npm install -g tree-sitter-cli\n' + + ' # or\n' + + ' brew install tree-sitter' + ); + } +} + +/** + * Detect available C compiler + */ +async function detectCCompiler(): Promise<string> { + const compilers = ['clang', 'gcc', 'cc']; + + for (const compiler of compilers) { + try { + await execa(compiler, ['--version'], { stdio: 'pipe' }); + return compiler; + } catch { + // Try next compiler + } + } + + throw new Error( + 'No C compiler found. Please install one:\n' + + ' macOS: xcode-select --install\n' + + ' Linux: sudo apt install build-essential (Ubuntu) or equivalent' + ); +} + +/** + * Detect runtime (bun vs npm) + */ +async function detectRuntime(): Promise<'bun' | 'npm'> { + try { + await execa('bun', ['--version'], { stdio: 'pipe' }); + return 'bun'; + } catch { + return 'npm'; + } +} + +/** + * Get project name from grammar.js or directory name + */ +function getProjectName(): string { + try { + const grammarContent = readFileSync('grammar.js', 'utf-8'); + const nameMatch = grammarContent.match(/name:\s*['"]([^'"]+)['"]/); + if (nameMatch) { + return nameMatch[1]; + } + } catch { + // Fall back to directory name + } + + return basename(process.cwd()); +} + +/** + * Ensure the current directory is treated as CommonJS for Node resolution, + * so that tree-sitter can load `grammar.js` (which uses module.exports). + * If no local package.json exists, create a minimal one with "type": "commonjs". + */ +function ensureCommonJsPackageJson(): void { + const packageJsonPath = 'package.json'; + if (!existsSync(packageJsonPath)) { + const projectName = basename(process.cwd()); + const minimal = { + name: `${projectName}-dsl`, + private: true, + type: 'commonjs', + description: `DSL project for ${projectName} generated by DSK`, + license: 'MIT' + } as const; + writeFileSync(packageJsonPath, JSON.stringify(minimal, null, 2)); + console.log(chalk.gray(' Created local package.json with { "type": "commonjs" }')); + } +} + +/** + * Generate C header file + */ +async function generateHeaderFile(projectName: string, includeDir: string): Promise<void> { + const headerContent = `#ifndef TREE_SITTER_${projectName.toUpperCase()}_H_ +#define TREE_SITTER_${projectName.toUpperCase()}_H_ + +typedef struct TSLanguage TSLanguage; + +#ifdef __cplusplus +extern "C" { +#endif + +const TSLanguage *tree_sitter_${projectName}(void); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_${projectName.toUpperCase()}_H_ +`; + + writeFileSync(join(includeDir, `${projectName}.h`), headerContent); +} + +/** + * Copy JS addon template files + */ +async function copyJSAddonTemplates(jsDir: string): Promise<void> { + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + const templateDir = join(__dirname, '..', '..', 'templates', 'js-addon'); + + if (!existsSync(templateDir)) { + throw new Error(`JS addon template directory not found: ${templateDir}`); + } + + // Copy all template files + const { processTemplate } = await import('../utils/template-processor.js'); + const projectName = getProjectName(); + + const templateContext = { + architecture: { + name: projectName, + paradigm: 'mixed' as const, + purpose: 'DSL', + dataPhilosophy: 'mixed' as const + }, + features: { controlFlow: [], dataStructures: [], functionTypes: [] }, + syntax: { + comments: { type: 'line_comment', pattern: '//' }, + identifiers: { pattern: '[a-zA-Z_][a-zA-Z0-9_]*', examples: ['identifier'] }, + numbers: { pattern: '\\d+', examples: ['42'] }, + strings: { pattern: '"[^"]*"', examples: ['"string"'] }, + variables: { keyword: 'let', operator: '=', terminator: ';', example: 'let x = 42;' }, + paradigmExamples: {} + } + }; + + processTemplate(templateDir, jsDir, templateContext); +} + +/** + * Copy source files from tree-sitter generation to JS package + */ +async function copySourceFiles(jsDir: string): Promise<void> { + const srcDir = 'src'; + const targetSrcDir = join(jsDir, 'src'); + + if (!existsSync(srcDir)) { + throw new Error('src/ directory not found. Run tree-sitter generate first.'); + } + + // Copy the entire src directory + cpSync(srcDir, targetSrcDir, { recursive: true }); + + console.log(chalk.gray(` Copied src files to ${targetSrcDir}`)); +} + +/** + * Update binding.gyp based on available source files + */ +async function updateBindingGyp(jsDir: string): Promise<void> { + const bindingGypPath = join(jsDir, 'binding.gyp'); + const scannerPath = join(jsDir, 'src', 'scanner.c'); + + if (existsSync(bindingGypPath)) { + let bindingGyp = JSON.parse(readFileSync(bindingGypPath, 'utf-8')); + + // Add scanner.c if it exists + if (existsSync(scannerPath)) { + const target = bindingGyp.targets[0]; + if (!target.sources.includes('src/scanner.c')) { + target.sources.push('src/scanner.c'); + console.log(chalk.gray(` Added scanner.c to build`)); + } + } else { + console.log(chalk.gray(` No scanner.c found, skipping`)); + } + + writeFileSync(bindingGypPath, JSON.stringify(bindingGyp, null, 2)); + } +} + +/** + * Update package.json in JS package + */ +async function updateJSPackageJson(jsDir: string): Promise<void> { + const projectName = getProjectName(); + const packageJsonPath = join(jsDir, 'package.json'); + + if (existsSync(packageJsonPath)) { + let packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + + // Update name and description + packageJson.name = `tree-sitter-${projectName}`; + packageJson.description = `Tree-sitter parser for ${projectName}`; + + // Update keywords + if (packageJson.keywords && Array.isArray(packageJson.keywords)) { + packageJson.keywords = packageJson.keywords.map((keyword: string) => + keyword === '__DSL_NAME__' ? projectName : keyword + ); + } + + writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); + } +} diff --git a/tree-sitter/dsk/dsk-cli/src/commands/dev.ts b/tree-sitter/dsk/dsk-cli/src/commands/dev.ts new file mode 100644 index 0000000..92b2867 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/commands/dev.ts @@ -0,0 +1,105 @@ +/** + * Dev Command - Watch grammar.js and rebuild/test on change + */ + +import { Command } from 'commander'; +import chokidar from 'chokidar'; +import chalk from 'chalk'; +import { existsSync } from 'fs'; +import { execa } from 'execa'; + +/** + * Create the dev command + */ +export function createDevCommand(): Command { + const devCommand = new Command('dev'); + + devCommand + .description('Watch grammar.js and run build + test on changes') + .option('-v, --verbose', 'Show detailed build output') + .option('--quiet', 'Suppress non-error logs') + .option('--debounce <ms>', 'Debounce delay in milliseconds', '150') + .action(async (options) => { + if (!existsSync('grammar.js')) { + console.error(chalk.red('❌ No grammar.js found. Are you in a DSL project directory?')); + process.exit(1); + } + + const verbose: boolean = Boolean(options.verbose); + const quiet: boolean = Boolean(options.quiet); + const debounceMs: number = Number.parseInt(options.debounce, 10) || 150; + + if (!quiet) { + console.log(chalk.blue('👀 Watching grammar.js for changes...')); + } + + // Initial build + test + await runBuildAndTest({ verbose, quiet }); + + // Watcher with debounced rebuilds + const watcher = chokidar.watch('grammar.js', { ignoreInitial: true }); + let isRunning = false; + let rerunRequested = false; + let debounceTimer: NodeJS.Timeout | null = null; + + const runOnce = async () => { + if (isRunning) { + rerunRequested = true; + return; + } + isRunning = true; + if (!quiet) { + console.log(chalk.yellow('↻ Change detected. Rebuilding...')); + } + try { + await runBuildAndTest({ verbose, quiet }); + if (!quiet) { + console.log(chalk.green('✅ Rebuild and tests completed.')); + } + } catch (e) { + // Errors already printed by build/test + } finally { + isRunning = false; + if (rerunRequested) { + rerunRequested = false; + runOnce(); + } + } + }; + + const debouncedTrigger = () => { + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(runOnce, debounceMs); + }; + + watcher.on('change', debouncedTrigger); + }); + + return devCommand; +} + +async function runBuildAndTest(opts: { verbose?: boolean; quiet?: boolean }): Promise<void> { + const { verbose, quiet } = opts; + if (!quiet) { + console.log(chalk.blue('🏗️ Building...')); + } + try { + const buildArgs = ['build', ...(verbose ? ['--verbose'] : [])]; + await execa('dsk', buildArgs, { stdio: 'inherit' }); + } catch (error: any) { + console.error(chalk.red('❌ Build failed. Fix errors and save again.')); + throw error; + } + + if (!quiet) { + console.log(chalk.blue('🧪 Testing...')); + } + try { + await execa('dsk', ['test'], { stdio: 'inherit' }); + } catch (error: any) { + console.error(chalk.red('❌ Tests failed. Fix tests and save again.')); + throw error; + } +} + + diff --git a/tree-sitter/dsk/dsk-cli/src/commands/highlight.ts b/tree-sitter/dsk/dsk-cli/src/commands/highlight.ts new file mode 100644 index 0000000..fd0d419 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/commands/highlight.ts @@ -0,0 +1,141 @@ +/** + * Highlight Command - Generate Tree-sitter highlights and editor scaffolds + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Create the highlight command + */ +export function createHighlightCommand(): Command { + const highlightCommand = new Command('highlight'); + + highlightCommand + .description('Generate Tree-sitter highlights and editor scaffolds (tree-sitter, neovim, emacs, vscode)') + .action(async () => { + if (!existsSync('grammar.js')) { + console.error(chalk.red('❌ No grammar.js found. Are you in a DSL project directory?')); + process.exit(1); + } + + const languageName = getProjectName(); + + // Tree-sitter highlights + const tsOutDir = 'generated/editors/tree-sitter'; + mkdirSync(tsOutDir, { recursive: true }); + const highlights = ` +; Minimal highlights scaffold. Extend as needed. + +; Comments +(comment) @comment + +; Strings +(string) @string + +; Numbers +(number) @number + +; Keywords (example) +[("if") ("else") ("let") ("function")] @keyword +`; + const tsFile = join(tsOutDir, 'highlights.scm'); + writeFileSync(tsFile, highlights.trim() + '\n', 'utf-8'); + console.log(chalk.green(`✅ Generated ${tsFile}`)); + + // Neovim instructions + const nvimDir = 'generated/editors/neovim'; + mkdirSync(nvimDir, { recursive: true }); + const nvimMd = `# Neovim setup for ${languageName} + +1. Copy queries to your runtimepath: queries/${languageName}/highlights.scm +2. Configure nvim-treesitter: + +\`\`\`lua +require('nvim-treesitter.configs').setup({ + ensure_installed = {}, + highlight = { enable = true }, +}) +\`\`\` +`; + writeFileSync(join(nvimDir, 'setup-instructions.md'), nvimMd, 'utf-8'); + console.log(chalk.green(`✅ Generated ${join(nvimDir, 'setup-instructions.md')}`)); + + // Emacs major mode (minimal) + const emacsDir = 'generated/editors/emacs'; + mkdirSync(emacsDir, { recursive: true }); + const emacsEl = `;;; ${languageName}-mode.el --- ${languageName} mode -*- lexical-binding: t; -*- + +(require 'treesit) + +(define-derived-mode ${languageName}-mode prog-mode "${languageName}" + "Major mode for ${languageName} using Tree-sitter." + (when (treesit-ready-p '${languageName}) + (treesit-parser-create '${languageName}))) + +(add-to-list 'auto-mode-alist '("\\.${languageName}$" . ${languageName}-mode)) + +(provide '${languageName}-mode) +`; + writeFileSync(join(emacsDir, `${languageName}-mode.el`), emacsEl, 'utf-8'); + console.log(chalk.green(`✅ Generated ${join(emacsDir, `${languageName}-mode.el`)}`)); + + // VS Code: TextMate grammar + language configuration (basic placeholders) + const vscodeSyntaxDir = 'generated/editors/vscode/syntaxes'; + const vscodeDir = 'generated/editors/vscode'; + mkdirSync(vscodeSyntaxDir, { recursive: true }); + mkdirSync(vscodeDir, { recursive: true }); + const scope = `source.${languageName}`; + const tmLanguage = { + name: languageName, + scopeName: scope, + patterns: [ + { include: '#comment' }, + { include: '#string' }, + { include: '#number' }, + { include: '#keyword' } + ], + repository: { + comment: { patterns: [{ name: 'comment.line.double-slash', match: '//.*$' }] }, + string: { patterns: [{ name: 'string.quoted.double', begin: '"', end: '"' }] }, + number: { patterns: [{ name: 'constant.numeric', match: '-?\\b[0-9]+(\\.[0-9]+)?\\b' }] }, + keyword: { patterns: [{ name: 'keyword.control', match: '\\b(if|else|let|function)\\b' }] } + } + } as const; + writeFileSync(join(vscodeSyntaxDir, `${languageName}.tmLanguage.json`), JSON.stringify(tmLanguage, null, 2)); + const langConfig = { + comments: { lineComment: '//' }, + brackets: [["{","}"],["[","]"],["(",")"]], + autoClosingPairs: [ + { open: '"', close: '"' }, + { open: '{', close: '}' }, + { open: '(', close: ')' }, + { open: '[', close: ']' } + ], + surroundingPairs: [ + { open: '"', close: '"' }, + { open: '{', close: '}' }, + { open: '(', close: ')' }, + { open: '[', close: ']' } + ] + }; + writeFileSync(join(vscodeDir, 'language-configuration.json'), JSON.stringify(langConfig, null, 2)); + console.log(chalk.green(`✅ Generated VS Code syntax and configuration`)); + }); + + return highlightCommand; +} + +function getProjectName(): string { + try { + const grammarContent = readFileSync('grammar.js', 'utf-8'); + const nameMatch = grammarContent.match(/name:\s*['"]([^'"]+)['"]/); + if (nameMatch) return nameMatch[1]; + } catch {} + const parts = process.cwd().split(/[\\/]/); + return parts[parts.length - 1] || 'dsl'; +} + + 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!`)); +} diff --git a/tree-sitter/dsk/dsk-cli/src/commands/package.ts b/tree-sitter/dsk/dsk-cli/src/commands/package.ts new file mode 100644 index 0000000..515c2ce --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/commands/package.ts @@ -0,0 +1,66 @@ +/** + * Package Command - Create distributable artifacts for C and JS outputs + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs'; +import { join } from 'path'; +import { execa } from 'execa'; + +/** + * Create the package command + */ +export function createPackageCommand(): Command { + const pkgCommand = new Command('package'); + + pkgCommand + .description('Package C and JS artifacts into distributable archives') + .action(async () => { + if (!existsSync('grammar.js')) { + console.error(chalk.red('❌ No grammar.js found. Are you in a DSL project directory?')); + process.exit(1); + } + + console.log(chalk.blue('📦 Packaging artifacts...')); + + // Ensure build is up to date + await execa('dsk', ['build'], { stdio: 'inherit' }); + + const distDir = 'dist'; + mkdirSync(distDir, { recursive: true }); + + // Zip C outputs (simple tar.gz using system tar to avoid extra deps) + const cDir = 'generated/c'; + if (existsSync(cDir)) { + const cArchive = join(distDir, 'c-artifacts.tar.gz'); + await execa('tar', ['-czf', cArchive, '-C', 'generated', 'c'], { stdio: 'inherit' }); + console.log(chalk.green(`✅ C artifacts: ${cArchive}`)); + } + + // Pack JS package + const jsDir = 'generated/js'; + if (existsSync(jsDir)) { + // Prefer bun pack, fallback to npm pack + let pkgPath = ''; + try { + const { stdout } = await execa('bun', ['pack'], { cwd: jsDir }); + pkgPath = stdout.trim(); + } catch { + const { stdout } = await execa('npm', ['pack'], { cwd: jsDir }); + pkgPath = stdout.trim(); + } + const fileName = pkgPath.split(/\s|\n/).pop() as string; + const srcPath = join(jsDir, fileName); + const destPath = join(distDir, fileName); + await execa('bash', ['-lc', `cp ${JSON.stringify(srcPath)} ${JSON.stringify(destPath)}`]); + console.log(chalk.green(`✅ JS package: ${destPath}`)); + } + + console.log(chalk.blue('🎉 Packaging complete.')); + }); + + return pkgCommand; +} + + diff --git a/tree-sitter/dsk/dsk-cli/src/commands/self.ts b/tree-sitter/dsk/dsk-cli/src/commands/self.ts new file mode 100644 index 0000000..2b43fd6 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/commands/self.ts @@ -0,0 +1,71 @@ +/** + * Self Packaging Command - Build and package the dsk CLI for distribution + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { execa } from 'execa'; +import { existsSync, mkdirSync, readFileSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +/** + * Create the self:package command + */ +export function createSelfPackageCommand(): Command { + const cmd = new Command('self:package'); + + cmd + .description('Build and package the dsk CLI into a .tgz for distribution') + .option('-v, --verbose', 'Show detailed build output') + .action(async (options) => { + const projectRoot = resolveProjectRoot(); + if (!existsSync(join(projectRoot, 'package.json'))) { + console.error(chalk.red('❌ Could not locate package.json for dsk-cli')); + process.exit(1); + } + + const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8')); + console.log(chalk.blue(`📦 Packaging ${pkg.name}@${pkg.version}`)); + + // 1) Build TypeScript → dist + console.log(chalk.blue('🏗️ Building CLI...')); + await execa('bun', ['x', 'tsc'], { cwd: projectRoot, stdio: options.verbose ? 'inherit' : 'inherit' }); + + // 2) Pack npm tarball using bun pack (fallback to npm pack) + console.log(chalk.blue('🧰 Creating package tarball...')); + let tgzName = ''; + try { + const { stdout } = await execa('bun', ['pack'], { cwd: projectRoot }); + tgzName = stdout.trim().split(/\s|\n/).pop() || ''; + } catch { + const { stdout } = await execa('npm', ['pack'], { cwd: projectRoot }); + tgzName = stdout.trim().split(/\s|\n/).pop() || ''; + } + + if (!tgzName) { + console.error(chalk.red('❌ Failed to determine generated package filename')); + process.exit(1); + } + + const releaseDir = join(projectRoot, 'release'); + mkdirSync(releaseDir, { recursive: true }); + const src = join(projectRoot, tgzName); + const dest = join(releaseDir, tgzName); + await execa('bash', ['-lc', `mv -f ${JSON.stringify(src)} ${JSON.stringify(dest)}`]); + + console.log(chalk.green(`✅ Created ${dest}`)); + }); + + return cmd; +} + +function resolveProjectRoot(): string { + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + // When compiled, this file lives under dist/commands. Project root is two levels up. + const candidate = join(__dirname, '..', '..'); + return candidate; +} + + diff --git a/tree-sitter/dsk/dsk-cli/src/commands/test.ts b/tree-sitter/dsk/dsk-cli/src/commands/test.ts new file mode 100644 index 0000000..694acc7 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/commands/test.ts @@ -0,0 +1,50 @@ +/** + * Test Command - Wrapper around `tree-sitter test` + * + * Streams test output directly to the console. + */ + +import { Command } from 'commander'; +import { execa } from 'execa'; +import chalk from 'chalk'; +import { existsSync } from 'fs'; + +/** + * Create the test command + */ +export function createTestCommand(): Command { + const testCommand = new Command('test'); + + testCommand + .description('Run tree-sitter tests and stream output') + .allowExcessArguments(true) + .option('-u, --update', 'Update expected outputs (snapshots)') + .option('-f, --filter <regex>', 'Only run tests whose descriptions match the regex') + .option('--cwd <dir>', 'Run tests with a different working directory') + .option('-v, --verbose', 'Show verbose output (passes through to tree-sitter)') + .argument('[patterns...]', 'Optional test patterns (filenames or test names)') + .action(async (patterns: string[], options) => { + if (!existsSync('grammar.js')) { + console.error(chalk.red('❌ No grammar.js found. Are you in a DSL project directory?')); + process.exit(1); + } + + try { + console.log(chalk.blue('🧪 Running tests...')); + const args = ['test']; + if (options.update) args.push('-u'); + if (options.filter) args.push('-f', String(options.filter)); + if (options.verbose) args.push('--verbose'); + args.push(...patterns); + await execa('tree-sitter', args, { stdio: 'inherit', cwd: options.cwd || process.cwd() }); + } catch (error: any) { + const message = error?.message || error || 'Unknown error'; + console.error(chalk.red('❌ Tests failed:'), message); + process.exit(1); + } + }); + + return testCommand; +} + + diff --git a/tree-sitter/dsk/dsk-cli/src/index.ts b/tree-sitter/dsk/dsk-cli/src/index.ts new file mode 100644 index 0000000..42c75c5 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/index.ts @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +/** + * DSK - DSL Development Kit + * Command-line tool for creating Domain-Specific Languages with Tree-sitter + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { readFileSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; + +// Get package.json for version info +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const packagePath = join(__dirname, '..', 'package.json'); +const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8')); + +const program = new Command(); + +// Configure main command +program + .name('dsk') + .description('DSL Development Kit - Streamline Domain-Specific Language creation') + .version(packageJson.version); + +// Welcome message for help +program.on('--help', () => { + console.log(''); + console.log(chalk.blue('Getting Started:')); + console.log(' $ dsk new my-language --interactive'); + console.log(' $ cd my-language'); + console.log(' $ dsk dev'); + console.log(''); + console.log(chalk.yellow('Learn more at: https://github.com/your-org/dsk')); +}); + +// Import and register commands +import { createNewCommand } from './commands/new.js'; +import { createBuildCommand } from './commands/build.js'; +import { createTestCommand } from './commands/test.js'; +import { createDevCommand } from './commands/dev.js'; +import { createHighlightCommand } from './commands/highlight.js'; +import { createPackageCommand } from './commands/package.js'; +import { createSelfPackageCommand } from './commands/self.js'; + +const newCommand = createNewCommand(); +const buildCommand = createBuildCommand(); +const testCommand = createTestCommand(); +const devCommand = createDevCommand(); +const highlightCommand = createHighlightCommand(); +const packageCommand = createPackageCommand(); +const selfPackageCommand = createSelfPackageCommand(); + +program.addCommand(newCommand); +program.addCommand(buildCommand); +program.addCommand(testCommand); +program.addCommand(devCommand); +program.addCommand(highlightCommand); +program.addCommand(packageCommand); +program.addCommand(selfPackageCommand); + +// TODO: Import remaining commands +// import { devCommand } from './commands/dev.js'; +// import { testCommand } from './commands/test.js'; +// import { highlightCommand } from './commands/highlight.js'; +// import { packageCommand } from './commands/package.js'; + +// program.addCommand(devCommand); +// program.addCommand(testCommand); +// program.addCommand(highlightCommand); +// program.addCommand(packageCommand); + + + +// legacy placeholder removed; real dev command registered above + +// Parse command line arguments +program.parse(); diff --git a/tree-sitter/dsk/dsk-cli/src/utils/grammar-generator.ts b/tree-sitter/dsk/dsk-cli/src/utils/grammar-generator.ts new file mode 100644 index 0000000..1b40eff --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/utils/grammar-generator.ts @@ -0,0 +1,466 @@ +/** + * Grammar Generator + * + * Converts collected user information into Tree-sitter grammar.js files + * with paradigm-aware rule generation + */ + +import { LanguageArchitecture, LanguageFeatures, LanguageSyntax } from '../commands/new.js'; + +export interface GrammarRule { + name: string; + definition: string; + comment?: string; +} + +export interface GeneratedGrammar { + name: string; + rules: GrammarRule[]; + extras: string[]; + conflicts: string[][]; + precedences: string[][]; + word?: string; +} + +/** + * Generate a complete Tree-sitter grammar from user specifications + */ +export function generateGrammar( + architecture: LanguageArchitecture, + features: LanguageFeatures, + syntax: LanguageSyntax +): GeneratedGrammar { + const rules: GrammarRule[] = []; + const extras: string[] = []; + + // Start with the root rule - this varies by paradigm + rules.push(generateRootRule(architecture, features)); + + // Add basic token rules + rules.push(...generateTokenRules(syntax)); + + // Add paradigm-specific rules + rules.push(...generateParadigmRules(architecture, features, syntax)); + + // Add data structure rules if specified + if (features.dataStructures.length > 0) { + rules.push(...generateDataStructureRules(features.dataStructures)); + } + + // Add control flow rules if specified + if (features.controlFlow.length > 0) { + rules.push(...generateControlFlowRules(features.controlFlow, syntax)); + } + + // Set up extras (whitespace and comments) + extras.push('/\\s/', `$.${getCommentRuleName(syntax.comments.pattern)}`); + + return { + name: architecture.name, + rules, + extras, + conflicts: [], // TODO: Add conflicts if needed + precedences: generatePrecedences(architecture, features), + word: 'identifier' // Most languages use identifier as word token + }; +} + +/** + * Generate the root rule based on language paradigm + */ +function generateRootRule(architecture: LanguageArchitecture, features: LanguageFeatures): GrammarRule { + let definition: string; + + switch (architecture.paradigm) { + case 'declarative': + definition = 'repeat(choice($.rule_declaration, $.constraint, $.fact))'; + break; + case 'functional': + definition = 'repeat(choice($.function_definition, $.expression, $.binding))'; + break; + case 'object-oriented': + definition = 'repeat(choice($.class_definition, $.statement, $.expression))'; + break; + case 'procedural': + case 'mixed': + default: + definition = 'repeat(choice($.statement, $.expression, $.declaration))'; + break; + } + + return { + name: 'source_file', + definition, + comment: `Root rule for ${architecture.paradigm} language` + }; +} + +/** + * Generate basic token rules (identifiers, numbers, strings, comments) + */ +function generateTokenRules(syntax: LanguageSyntax): GrammarRule[] { + const rules: GrammarRule[] = []; + + // Identifier + rules.push({ + name: 'identifier', + definition: `/${syntax.identifiers.pattern}/`, + comment: `Identifiers: ${syntax.identifiers.examples.join(', ')}` + }); + + // Numbers + rules.push({ + name: 'number', + definition: `/${syntax.numbers.pattern}/`, + comment: `Numbers: ${syntax.numbers.examples.join(', ')}` + }); + + // Strings + rules.push({ + name: 'string', + definition: `/${syntax.strings.pattern}/`, + comment: `Strings: ${syntax.strings.examples.join(', ')}` + }); + + // Comments + const commentRuleName = getCommentRuleName(syntax.comments.pattern); + rules.push({ + name: commentRuleName, + definition: `/${escapeRegex(syntax.comments.pattern)}.*$/`, + comment: `Line comments starting with ${syntax.comments.pattern}` + }); + + return rules; +} + +/** + * Generate paradigm-specific rules + */ +function generateParadigmRules( + architecture: LanguageArchitecture, + features: LanguageFeatures, + syntax: LanguageSyntax +): GrammarRule[] { + const rules: GrammarRule[] = []; + + // Variable declarations (common to most paradigms) + rules.push({ + name: 'variable_declaration', + definition: `seq("${syntax.variables.keyword}", $.identifier, "${syntax.variables.operator}", $.expression, "${syntax.variables.terminator}")`, + comment: `Variable declarations: ${syntax.variables.example}` + }); + + // Expression rule (fundamental to all paradigms) + rules.push(generateExpressionRule(architecture, features)); + + // Statement rule (for imperative paradigms) + if (['procedural', 'object-oriented', 'mixed'].includes(architecture.paradigm)) { + rules.push(generateStatementRule(architecture, features)); + } + + // Add paradigm-specific constructs + switch (architecture.paradigm) { + case 'object-oriented': + if (syntax.paradigmExamples.class) { + rules.push(generateClassRule(syntax.paradigmExamples.class)); + } + break; + case 'functional': + if (syntax.paradigmExamples.function) { + rules.push(generateFunctionRule(syntax.paradigmExamples.function, features.functionTypes)); + } + break; + case 'declarative': + if (syntax.paradigmExamples.rule) { + rules.push(generateDeclarativeRule(syntax.paradigmExamples.rule)); + } + break; + } + + return rules; +} + +/** + * Generate expression rule based on paradigm + */ +function generateExpressionRule(architecture: LanguageArchitecture, features: LanguageFeatures): GrammarRule { + const choices = [ + '$.identifier', + '$.number', + '$.string', + '$.parenthesized_expression' + ]; + + // Add function calls if functions are supported + if (features.functionTypes.length > 0) { + choices.push('$.function_call'); + } + + // Add data structure literals + if (features.dataStructures.includes('arrays')) { + choices.push('$.array_literal'); + } + if (features.dataStructures.includes('objects')) { + choices.push('$.object_literal'); + } + + // Add binary operations for most paradigms + if (architecture.paradigm !== 'declarative') { + choices.push('$.binary_expression'); + } + + return { + name: 'expression', + definition: `choice(${choices.join(', ')})`, + comment: 'Expression rule covering all expression types' + }; +} + +/** + * Generate statement rule for imperative paradigms + */ +function generateStatementRule(architecture: LanguageArchitecture, features: LanguageFeatures): GrammarRule { + const choices = [ + '$.variable_declaration', + '$.expression_statement' + ]; + + // Add control flow statements + if (features.controlFlow.includes('conditionals')) { + choices.push('$.if_statement'); + } + if (features.controlFlow.includes('loops')) { + choices.push('$.for_statement', '$.while_statement'); + } + + return { + name: 'statement', + definition: `choice(${choices.join(', ')})`, + comment: 'Statement rule for imperative constructs' + }; +} + +/** + * Generate data structure rules + */ +function generateDataStructureRules(dataStructures: string[]): GrammarRule[] { + const rules: GrammarRule[] = []; + + if (dataStructures.includes('arrays')) { + rules.push({ + name: 'array_literal', + definition: 'seq("[", optional(seq($.expression, repeat(seq(",", $.expression)))), "]")', + comment: 'Array literals: [1, 2, 3]' + }); + } + + if (dataStructures.includes('objects')) { + rules.push({ + name: 'object_literal', + definition: 'seq("{", optional(seq($.property, repeat(seq(",", $.property)))), "}")', + comment: 'Object literals: {key: value}' + }); + + rules.push({ + name: 'property', + definition: 'seq(choice($.identifier, $.string), ":", $.expression)', + comment: 'Object property: key: value' + }); + } + + if (dataStructures.includes('tuples')) { + rules.push({ + name: 'tuple_literal', + definition: 'seq("(", $.expression, repeat1(seq(",", $.expression)), ")")', + comment: 'Tuple literals: (a, b, c)' + }); + } + + return rules; +} + +/** + * Generate control flow rules + */ +function generateControlFlowRules(controlFlow: string[], syntax: LanguageSyntax): GrammarRule[] { + const rules: GrammarRule[] = []; + + if (controlFlow.includes('conditionals')) { + rules.push({ + name: 'if_statement', + definition: 'seq("if", "(", $.expression, ")", $.block, optional(seq("else", choice($.if_statement, $.block))))', + comment: 'If-else statements' + }); + + rules.push({ + name: 'block', + definition: 'seq("{", repeat($.statement), "}")', + comment: 'Code blocks' + }); + } + + if (controlFlow.includes('loops')) { + rules.push({ + name: 'while_statement', + definition: 'seq("while", "(", $.expression, ")", $.block)', + comment: 'While loops' + }); + + rules.push({ + name: 'for_statement', + definition: 'seq("for", "(", optional($.statement), ";", optional($.expression), ";", optional($.expression), ")", $.block)', + comment: 'For loops' + }); + } + + return rules; +} + +/** + * Generate class rule from user example + */ +function generateClassRule(classExample: string): GrammarRule { + // Simple class rule - could be enhanced with more parsing + return { + name: 'class_definition', + definition: 'seq("class", $.identifier, "{", repeat($.method_definition), "}")', + comment: `Class definition based on: ${classExample}` + }; +} + +/** + * Generate function rule from user example + */ +function generateFunctionRule(functionExample: string, functionTypes: string[]): GrammarRule { + let definition = 'seq("function", $.identifier, "(", optional($.parameter_list), ")", $.block)'; + + // Add arrow functions if supported + if (functionTypes.includes('anonymous')) { + definition = `choice(${definition}, $.arrow_function)`; + } + + return { + name: 'function_definition', + definition, + comment: `Function definition based on: ${functionExample}` + }; +} + +/** + * Generate declarative rule from user example + */ +function generateDeclarativeRule(ruleExample: string): GrammarRule { + return { + name: 'rule_declaration', + definition: 'seq("rule", $.identifier, optional(seq("when", $.expression)))', + comment: `Rule declaration based on: ${ruleExample}` + }; +} + +/** + * Generate precedences based on paradigm + */ +function generatePrecedences(architecture: LanguageArchitecture, features: LanguageFeatures): string[][] { + // Basic precedence for binary operations + const precedences = [ + ['$.binary_expression'] + ]; + + // Add function call precedence if functions are supported + if (features.functionTypes.length > 0) { + precedences.push(['$.function_call']); + } + + return precedences; +} + +/** + * Generate the complete grammar.js file content + */ +export function generateGrammarFile(grammar: GeneratedGrammar): string { + const lines: string[] = []; + + lines.push('/**'); + lines.push(` * Grammar for ${grammar.name}`); + lines.push(' * Generated by DSK (DSL Development Kit)'); + lines.push(' */'); + lines.push(''); + lines.push('module.exports = grammar({'); + lines.push(` name: '${grammar.name}',`); + lines.push(''); + + // Add word token if specified + if (grammar.word) { + lines.push(` word: $ => $.${grammar.word},`); + lines.push(''); + } + + // Add rules + lines.push(' rules: {'); + + grammar.rules.forEach((rule, index) => { + if (rule.comment) { + lines.push(` // ${rule.comment}`); + } + lines.push(` ${rule.name}: $ => ${rule.definition}${index < grammar.rules.length - 1 ? ',' : ''}`); + if (index < grammar.rules.length - 1) { + lines.push(''); + } + }); + + lines.push(' }'); + + // Add extras + if (grammar.extras.length > 0) { + lines.push(','); + lines.push(''); + lines.push(' extras: $ => ['); + grammar.extras.forEach((extra, index) => { + lines.push(` ${extra}${index < grammar.extras.length - 1 ? ',' : ''}`); + }); + lines.push(' ]'); + } + + // Add conflicts if any + if (grammar.conflicts.length > 0) { + lines.push(','); + lines.push(''); + lines.push(' conflicts: $ => ['); + grammar.conflicts.forEach((conflict, index) => { + lines.push(` [${conflict.join(', ')}]${index < grammar.conflicts.length - 1 ? ',' : ''}`); + }); + lines.push(' ]'); + } + + // Add precedences if any + if (grammar.precedences.length > 0) { + lines.push(','); + lines.push(''); + lines.push(' precedences: $ => ['); + grammar.precedences.forEach((prec, index) => { + lines.push(` [${prec.join(', ')}]${index < grammar.precedences.length - 1 ? ',' : ''}`); + }); + lines.push(' ]'); + } + + lines.push('});'); + lines.push(''); + + return lines.join('\n'); +} + +/** + * Helper functions + */ +function getCommentRuleName(commentPattern: string): string { + switch (commentPattern) { + case '//': return 'line_comment_slash'; + case '#': return 'line_comment_hash'; + case ';': return 'line_comment_semicolon'; + default: return 'line_comment'; + } +} + +function escapeRegex(pattern: string): string { + return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} diff --git a/tree-sitter/dsk/dsk-cli/src/utils/inference.ts b/tree-sitter/dsk/dsk-cli/src/utils/inference.ts new file mode 100644 index 0000000..f49e176 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/utils/inference.ts @@ -0,0 +1,286 @@ +/** + * Pattern Inference Engine + * + * Infers regular expression patterns from user examples. + * Uses an extensible library of common token patterns with solid defaults. + */ + +export interface TokenPattern { + name: string; + description: string; + regex: RegExp; + examples: string[]; + priority: number; // Higher priority patterns are tried first +} + +export interface InferenceResult { + pattern: TokenPattern | null; + confidence: number; // 0-1 score indicating match quality + matchedExamples: string[]; + rejectedExamples: string[]; +} + +/** + * Default token pattern library with common programming language constructs + */ +export const DEFAULT_PATTERNS: TokenPattern[] = [ + // Identifiers + { + name: 'c_identifier', + description: 'C-style identifier (letters, digits, underscore, must start with letter/underscore)', + regex: /^[a-zA-Z_][a-zA-Z0-9_]*$/, + examples: ['myVar', 'userName', '_private', 'MAX_SIZE'], + priority: 10 + }, + { + name: 'js_identifier', + description: 'JavaScript-style identifier (letters, digits, $, _, must start with letter/$/_)', + regex: /^[A-Za-z_$][A-Za-z0-9_$]*$/, + examples: ['x', 'var1', '$var', '_var', 'Var3', 'BananaFruitStand'], + priority: 11 + }, + { + name: 'kebab_identifier', + description: 'Kebab-case identifier (letters, digits, hyphens)', + regex: /^[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]$/, + examples: ['my-var', 'user-name', 'max-size'], + priority: 8 + }, + { + name: 'camel_identifier', + description: 'CamelCase identifier (letters and digits, no separators)', + regex: /^[a-zA-Z][a-zA-Z0-9]*$/, + examples: ['myVar', 'userName', 'maxSize'], + priority: 9 + }, + + // Numbers + { + name: 'number_general', + description: 'Integer or floating point number (optional sign)', + regex: /^[+-]?(?:\d*\.\d+|\d+\.\d*|\d+)$/, + examples: ['1', '-7', '1.24', '10000', '+0.5', '2.'], + priority: 16 + }, + { + name: 'integer', + description: 'Integer number (optional sign, digits)', + regex: /^[+-]?\d+$/, + examples: ['42', '-17', '+123', '0'], + priority: 15 + }, + { + name: 'float', + description: 'Floating point number (optional sign, decimal point)', + regex: /^[+-]?\d*\.\d+$/, + examples: ['3.14', '-2.5', '+0.123', '.5'], + priority: 14 + }, + { + name: 'scientific', + description: 'Scientific notation number', + regex: /^[+-]?\d*\.?\d+[eE][+-]?\d+$/, + examples: ['1e10', '3.14e-2', '-2.5E+3'], + priority: 12 + }, + { + name: 'hex_number', + description: 'Hexadecimal number (0x prefix)', + regex: /^0[xX][0-9a-fA-F]+$/, + examples: ['0xFF', '0x123ABC', '0X00'], + priority: 13 + }, + + // Strings + { + name: 'double_quoted_string', + description: 'Double-quoted string literal', + regex: /^".*"$/, + examples: ['"hello"', '"world"', '""'], + priority: 11 + }, + { + name: 'single_quoted_string', + description: 'Single-quoted string literal', + regex: /^'.*'$/, + examples: ["'hello'", "'world'", "''"], + priority: 11 + }, + { + name: 'backtick_string', + description: 'Backtick-quoted string literal (template strings)', + regex: /^`.*`$/, + examples: ['`hello`', '`world ${var}`', '``'], + priority: 7 + }, + + // Comments + { + name: 'c_line_comment', + description: 'C-style line comment (// prefix)', + regex: /^\/\/.*$/, + examples: ['// comment', '// TODO: fix this'], + priority: 16 + }, + { + name: 'hash_line_comment', + description: 'Hash line comment (# prefix)', + regex: /^#.*$/, + examples: ['# comment', '# TODO: fix this'], + priority: 16 + }, + { + name: 'semicolon_line_comment', + description: 'Semicolon line comment (; prefix)', + regex: /^;.*$/, + examples: ['; comment', '; TODO: fix this'], + priority: 16 + }, + + // Special patterns + { + name: 'boolean', + description: 'Boolean literal', + regex: /^(true|false)$/, + examples: ['true', 'false'], + priority: 17 + }, + { + name: 'null_literal', + description: 'Null/nil literal', + regex: /^(null|nil|None|undefined)$/, + examples: ['null', 'nil', 'None', 'undefined'], + priority: 17 + } +]; + +/** + * Infer a pattern from valid and invalid examples + */ +export function inferPattern( + validExamples: string[], + invalidExamples: string[] = [], + customPatterns: TokenPattern[] = [] +): InferenceResult { + if (validExamples.length === 0) { + return { + pattern: null, + confidence: 0, + matchedExamples: [], + rejectedExamples: invalidExamples + }; + } + + // Combine default patterns with custom patterns + const allPatterns = [...customPatterns, ...DEFAULT_PATTERNS] + .sort((a, b) => b.priority - a.priority); + + // Try each pattern + for (const pattern of allPatterns) { + const validMatches = validExamples.filter(example => pattern.regex.test(example)); + const invalidMatches = invalidExamples.filter(example => pattern.regex.test(example)); + + // Pattern must match ALL valid examples and NO invalid examples + if (validMatches.length === validExamples.length && invalidMatches.length === 0) { + const confidence = calculateConfidence(validExamples, invalidExamples, pattern); + + return { + pattern, + confidence, + matchedExamples: validMatches, + rejectedExamples: invalidExamples + }; + } + } + + // No pattern found + return { + pattern: null, + confidence: 0, + matchedExamples: [], + rejectedExamples: invalidExamples + }; +} + +/** + * Calculate confidence score for a pattern match + */ +function calculateConfidence( + validExamples: string[], + invalidExamples: string[], + pattern: TokenPattern +): number { + let confidence = 0.8; // Base confidence + + // Boost confidence for more valid examples + if (validExamples.length >= 3) confidence += 0.1; + if (validExamples.length >= 5) confidence += 0.05; + + // Boost confidence for having invalid examples that were correctly rejected + if (invalidExamples.length > 0) confidence += 0.05; + + // Boost confidence if examples match the pattern's own examples + const patternExampleMatches = validExamples.filter(ex => + pattern.examples.some(pex => ex === pex) + ); + if (patternExampleMatches.length > 0) { + confidence += 0.05 * patternExampleMatches.length; + } + + return Math.min(confidence, 1.0); +} + +/** + * Generate a custom regex pattern from examples (fallback when inference fails) + */ +export function generateCustomPattern( + validExamples: string[], + invalidExamples: string[] = [] +): string { + if (validExamples.length === 0) return ''; + + // Simple approach: create alternation of literal examples + // This is a fallback - not as robust as proper pattern matching + const escapedExamples = validExamples.map(ex => + ex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ); + + return `^(${escapedExamples.join('|')})$`; +} + +/** + * Validate that a pattern works correctly with given examples + */ +export function validatePattern( + pattern: string, + validExamples: string[], + invalidExamples: string[] = [] +): { isValid: boolean; errors: string[] } { + const errors: string[] = []; + + try { + const regex = new RegExp(pattern); + + // Check valid examples + for (const example of validExamples) { + if (!regex.test(example)) { + errors.push(`Pattern does not match valid example: "${example}"`); + } + } + + // Check invalid examples + for (const example of invalidExamples) { + if (regex.test(example)) { + errors.push(`Pattern incorrectly matches invalid example: "${example}"`); + } + } + + } catch (e) { + errors.push(`Invalid regular expression: ${e instanceof Error ? e.message : 'Unknown error'}`); + } + + return { + isValid: errors.length === 0, + errors + }; +} diff --git a/tree-sitter/dsk/dsk-cli/src/utils/template-processor.ts b/tree-sitter/dsk/dsk-cli/src/utils/template-processor.ts new file mode 100644 index 0000000..e237048 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/src/utils/template-processor.ts @@ -0,0 +1,192 @@ +/** + * Template Processor + * + * Processes template files by replacing placeholders with actual values + */ + +import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, copyFileSync } from 'fs'; +import { join, dirname, basename, extname } from 'path'; +import { LanguageArchitecture, LanguageFeatures, LanguageSyntax } from '../commands/new.js'; + +export interface TemplateContext { + architecture: LanguageArchitecture; + features: LanguageFeatures; + syntax: LanguageSyntax; +} + +/** + * Process a template directory and create a project + */ +export function processTemplate( + templateDir: string, + outputDir: string, + context: TemplateContext +): void { + // Create output directory + mkdirSync(outputDir, { recursive: true }); + + // Process all files in template directory + processDirectory(templateDir, outputDir, context); +} + +/** + * Process a directory recursively + */ +function processDirectory( + sourceDir: string, + targetDir: string, + context: TemplateContext +): void { + const items = readdirSync(sourceDir); + + for (const item of items) { + const sourcePath = join(sourceDir, item); + const targetPath = join(targetDir, processFileName(item, context)); + + const stat = statSync(sourcePath); + + if (stat.isDirectory()) { + mkdirSync(targetPath, { recursive: true }); + processDirectory(sourcePath, targetPath, context); + } else { + processFile(sourcePath, targetPath, context); + } + } +} + +/** + * Process a single file + */ +function processFile( + sourcePath: string, + targetPath: string, + context: TemplateContext +): void { + const content = readFileSync(sourcePath, 'utf-8'); + const processedContent = processContent(content, context); + + // Ensure target directory exists + mkdirSync(dirname(targetPath), { recursive: true }); + + writeFileSync(targetPath, processedContent, 'utf-8'); +} + +/** + * Process file name placeholders + */ +function processFileName(fileName: string, context: TemplateContext): string { + const replacements = getReplacements(context); + + let processed = fileName; + for (const [placeholder, value] of Object.entries(replacements)) { + processed = processed.replace(new RegExp(placeholder, 'g'), value); + } + + return processed; +} + +/** + * Process file content placeholders + */ +function processContent(content: string, context: TemplateContext): string { + const replacements = getReplacements(context); + + let processed = content; + for (const [placeholder, value] of Object.entries(replacements)) { + processed = processed.replace(new RegExp(placeholder, 'g'), value); + } + + return processed; +} + +/** + * Get all placeholder replacements + */ +function getReplacements(context: TemplateContext): Record<string, string> { + const { architecture, features, syntax } = context; + + return { + '__DSL_NAME__': architecture.name, + '__PARADIGM__': architecture.paradigm, + '__PURPOSE__': architecture.purpose, + '__DATA_PHILOSOPHY__': architecture.dataPhilosophy, + + // Syntax elements + '__VARIABLE_KEYWORD__': syntax.variables.keyword, + '__ASSIGNMENT_OPERATOR__': syntax.variables.operator, + '__TERMINATOR__': syntax.variables.terminator, + '__VARIABLE_EXAMPLE__': syntax.variables.example, + + '__COMMENT_EXAMPLE__': syntax.comments.pattern, + '__STRING_EXAMPLE__': syntax.strings.examples[0] || '"hello"', + '__NUMBER_EXAMPLE__': syntax.numbers.examples[0] || '42', + '__IDENTIFIER_EXAMPLE__': syntax.identifiers.examples[0] || 'myVar', + + // Paradigm-specific examples + '__PARADIGM_EXAMPLE__': getParadigmExample(architecture, syntax), + + // File extension + '__EXT__': getFileExtension(architecture.name), + + // Feature lists + '__FEATURES_LIST__': generateFeaturesList(features), + '__DATA_STRUCTURES__': features.dataStructures.join(', ') || 'Basic types', + + // Control flow + '__CONTROL_FLOW__': features.controlFlow.join(', ') || 'Sequential execution' + }; +} + +/** + * Get paradigm-specific example code + */ +function getParadigmExample(architecture: LanguageArchitecture, syntax: LanguageSyntax): string { + if (syntax.paradigmExamples.class) { + return syntax.paradigmExamples.class; + } + if (syntax.paradigmExamples.function) { + return syntax.paradigmExamples.function; + } + if (syntax.paradigmExamples.rule) { + return syntax.paradigmExamples.rule; + } + + // Fallback to variable example + return syntax.variables.example; +} + +/** + * Get appropriate file extension for the language + */ +function getFileExtension(languageName: string): string { + // Simple heuristic - could be made configurable + const name = languageName.toLowerCase(); + + if (name.includes('script')) return 'script'; + if (name.includes('lang')) return 'lang'; + if (name.includes('dsl')) return 'dsl'; + + // Default: use first 3 characters of name + return name.substring(0, 3); +} + +/** + * Generate features list for README + */ +function generateFeaturesList(features: LanguageFeatures): string { + const items: string[] = []; + + if (features.controlFlow.length > 0) { + items.push(`- **Control Flow**: ${features.controlFlow.join(', ')}`); + } + + if (features.dataStructures.length > 0) { + items.push(`- **Data Structures**: ${features.dataStructures.join(', ')}`); + } + + if (features.functionTypes.length > 0) { + items.push(`- **Functions**: ${features.functionTypes.join(', ')}`); + } + + return items.length > 0 ? items.join('\n') : '- Basic language constructs'; +} diff --git a/tree-sitter/dsk/dsk-cli/templates/default/README.md b/tree-sitter/dsk/dsk-cli/templates/default/README.md new file mode 100644 index 0000000..4560c6a --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/default/README.md @@ -0,0 +1,82 @@ +# __DSL_NAME__ + +A Domain-Specific Language created with DSK (DSL Development Kit). + +## Overview + +- **Language Type**: __PARADIGM__ +- **Purpose**: __PURPOSE__ +- **Data Philosophy**: __DATA_PHILOSOPHY__ + +## Getting Started + +### Prerequisites + +- [Tree-sitter CLI](https://tree-sitter.github.io/tree-sitter/creating-parsers#installation) +- Node.js or Bun (for JavaScript bindings) +- C compiler (gcc/clang) for native compilation + +### Development + +```bash +# Start development mode with file watching +dsk dev + +# Run tests +dsk test + +# Build parser and packages +dsk build + +# Generate editor syntax highlighting +dsk highlight + +# Package for distribution +dsk package +``` + +## Project Structure + +``` +__DSL_NAME__/ +├── grammar.js # Tree-sitter grammar definition +├── corpus/ # Test cases for the grammar +│ └── examples.txt +├── generated/ # Generated artifacts (created by dsk build) +│ ├── c/ # C static library +│ ├── js/ # JavaScript/Node.js package +│ └── editors/ # Editor configurations +└── examples/ # Example programs in your language + └── hello.__EXT__ +``` + +## Language Features + +__FEATURES_LIST__ + +## Grammar Rules + +The grammar includes rules for: + +- **Tokens**: Identifiers, numbers, strings, comments +- **Expressions**: Based on your language paradigm +- **Statements**: Control flow and declarations +- **Data Structures**: __DATA_STRUCTURES__ + +## Next Steps + +1. **Customize the Grammar**: Edit `grammar.js` to refine your language syntax +2. **Add Test Cases**: Create examples in `corpus/examples.txt` +3. **Write Example Programs**: Add sample code to `examples/` +4. **Generate Editor Support**: Run `dsk highlight` for syntax highlighting +5. **Build and Test**: Use `dsk dev` for iterative development + +## Resources + +- [Tree-sitter Documentation](https://tree-sitter.github.io/tree-sitter/) +- [DSK Documentation](https://github.com/your-org/dsk) +- [Language Implementation Patterns](https://pragprog.com/titles/tpdsl/) + +--- + +Generated by [DSK](https://github.com/your-org/dsk) - DSL Development Kit diff --git a/tree-sitter/dsk/dsk-cli/templates/default/corpus/examples.txt b/tree-sitter/dsk/dsk-cli/templates/default/corpus/examples.txt new file mode 100644 index 0000000..94f199e --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/default/corpus/examples.txt @@ -0,0 +1,85 @@ +================================================================================ +Basic Examples +================================================================================ + +Simple variable declaration +--- + +__VARIABLE_EXAMPLE__ + +--- + +(source_file + (statement + (variable_declaration + (identifier) + (expression (number))))) + +================================================================================ +Comments +================================================================================ + +Line comment +--- + +__COMMENT_EXAMPLE__ This is a comment +__VARIABLE_EXAMPLE__ + +--- + +(source_file + (statement + (variable_declaration + (identifier) + (expression (number))))) + +================================================================================ +Expressions +================================================================================ + +String literal via variable declaration +--- + +__VARIABLE_KEYWORD__ message __ASSIGNMENT_OPERATOR__ __STRING_EXAMPLE____TERMINATOR__ + +--- + +(source_file + (statement + (variable_declaration + (identifier) + (expression (string))))) + +================================================================================ +Numbers +================================================================================ + +Integer via variable declaration +--- + +__VARIABLE_KEYWORD__ count __ASSIGNMENT_OPERATOR__ __NUMBER_EXAMPLE____TERMINATOR__ + +--- + +(source_file + (statement + (variable_declaration + (identifier) + (expression (number))))) + +================================================================================ +Identifiers +================================================================================ + +Valid identifier via variable declaration +--- + +__VARIABLE_KEYWORD__ __IDENTIFIER_EXAMPLE__ __ASSIGNMENT_OPERATOR__ 42__TERMINATOR__ + +--- + +(source_file + (statement + (variable_declaration + (identifier) + (expression (number))))) diff --git a/tree-sitter/dsk/dsk-cli/templates/default/examples/hello.__EXT__ b/tree-sitter/dsk/dsk-cli/templates/default/examples/hello.__EXT__ new file mode 100644 index 0000000..07769f6 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/default/examples/hello.__EXT__ @@ -0,0 +1,7 @@ +__COMMENT_EXAMPLE__ Hello World example for __DSL_NAME__ + +__PARADIGM_EXAMPLE__ + +__COMMENT_EXAMPLE__ More examples: +__VARIABLE_EXAMPLE__ +__VARIABLE_KEYWORD__ __IDENTIFIER_EXAMPLE__ __ASSIGNMENT_OPERATOR__ __STRING_EXAMPLE____TERMINATOR__ diff --git a/tree-sitter/dsk/dsk-cli/templates/default/package.json b/tree-sitter/dsk/dsk-cli/templates/default/package.json new file mode 100644 index 0000000..ab3d0c4 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/default/package.json @@ -0,0 +1,9 @@ +{ + "name": "__DSL_NAME__-dsl", + "private": true, + "type": "commonjs", + "description": "DSL project for __DSL_NAME__ generated by DSK", + "license": "MIT" +} + + diff --git a/tree-sitter/dsk/dsk-cli/templates/js-addon/binding.gyp b/tree-sitter/dsk/dsk-cli/templates/js-addon/binding.gyp new file mode 100644 index 0000000..ddb424b --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/js-addon/binding.gyp @@ -0,0 +1,28 @@ +{ + "targets": [ + { + "target_name": "tree_sitter___DSL_NAME___binding", + "dependencies": [ + "<!(node -p \"require('node-addon-api').gyp\")" + ], + "include_dirs": [ + "<!@(node -p \"require('node-addon-api').include\")", + "src" + ], + "sources": [ + "bindings/node.cc", + "src/parser.c" + ], + "cflags_c": [ + "-std=c99" + ], + "cflags_cc": [ + "-std=c++14" + ], + "defines": [ + "NAPI_DISABLE_CPP_EXCEPTIONS", + "NAPI_VERSION=8" + ] + } + ] +} diff --git a/tree-sitter/dsk/dsk-cli/templates/js-addon/bindings/node.cc b/tree-sitter/dsk/dsk-cli/templates/js-addon/bindings/node.cc new file mode 100644 index 0000000..d0ba098 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/js-addon/bindings/node.cc @@ -0,0 +1,16 @@ +#include "napi.h" + +typedef struct TSLanguage TSLanguage; + +extern "C" TSLanguage *tree_sitter___DSL_NAME__(); + +// "tree_sitter___DSL_NAME___binding" is the symbol that gets exported +// when this file is compiled as a Node.js addon. +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports["name"] = Napi::String::New(env, "__DSL_NAME__"); + auto language = tree_sitter___DSL_NAME__(); + exports["language"] = Napi::External<TSLanguage>::New(env, language); + return exports; +} + +NODE_API_MODULE(tree_sitter___DSL_NAME___binding, Init) diff --git a/tree-sitter/dsk/dsk-cli/templates/js-addon/index.d.ts b/tree-sitter/dsk/dsk-cli/templates/js-addon/index.d.ts new file mode 100644 index 0000000..d25eae0 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/js-addon/index.d.ts @@ -0,0 +1,3 @@ +declare const _exports: any; +export = _exports; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/tree-sitter/dsk/dsk-cli/templates/js-addon/index.d.ts.map b/tree-sitter/dsk/dsk-cli/templates/js-addon/index.d.ts.map new file mode 100644 index 0000000..ca7a93a --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/js-addon/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":""} \ No newline at end of file diff --git a/tree-sitter/dsk/dsk-cli/templates/js-addon/index.js b/tree-sitter/dsk/dsk-cli/templates/js-addon/index.js new file mode 100644 index 0000000..4780f31 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/js-addon/index.js @@ -0,0 +1,15 @@ +try { + module.exports = require("./build/Release/tree_sitter___DSL_NAME___binding"); +} catch (error1) { + if (error1.code !== 'MODULE_NOT_FOUND') { + throw error1; + } + try { + module.exports = require("./build/Debug/tree_sitter___DSL_NAME___binding"); + } catch (error2) { + if (error2.code !== 'MODULE_NOT_FOUND') { + throw error2; + } + throw error1 + } +} diff --git a/tree-sitter/dsk/dsk-cli/templates/js-addon/package.json b/tree-sitter/dsk/dsk-cli/templates/js-addon/package.json new file mode 100644 index 0000000..c7a0488 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/templates/js-addon/package.json @@ -0,0 +1,31 @@ +{ + "name": "tree-sitter-__DSL_NAME__", + "version": "1.0.0", + "description": "Tree-sitter parser for __DSL_NAME__", + "main": "index.js", + "keywords": [ + "tree-sitter", + "parser", + "__DSL_NAME__" + ], + "author": "Generated by DSK", + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.0.0", + "node-gyp": "^10.0.0" + }, + "devDependencies": { + "tree-sitter-cli": "^0.20.0" + }, + "scripts": { + "install": "node-gyp rebuild", + "test": "tree-sitter test" + }, + "gypfile": true, + "files": [ + "grammar.js", + "src", + "index.js", + "binding.gyp" + ] +} diff --git a/tree-sitter/dsk/dsk-cli/test-inference.d.ts b/tree-sitter/dsk/dsk-cli/test-inference.d.ts new file mode 100644 index 0000000..53c18d9 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/test-inference.d.ts @@ -0,0 +1,5 @@ +/** + * Quick test of the inference engine + */ +export {}; +//# sourceMappingURL=test-inference.d.ts.map \ No newline at end of file diff --git a/tree-sitter/dsk/dsk-cli/test-inference.d.ts.map b/tree-sitter/dsk/dsk-cli/test-inference.d.ts.map new file mode 100644 index 0000000..ecf80f8 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/test-inference.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"test-inference.d.ts","sourceRoot":"","sources":["test-inference.ts"],"names":[],"mappings":"AAAA;;GAEG"} \ No newline at end of file diff --git a/tree-sitter/dsk/dsk-cli/test-inference.js.map b/tree-sitter/dsk/dsk-cli/test-inference.js.map new file mode 100644 index 0000000..5d9b120 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/test-inference.js.map @@ -0,0 +1 @@ +{"version":3,"file":"test-inference.js","sourceRoot":"","sources":["test-inference.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;AAErD,8BAA8B;AAC9B,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;AAC3C,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AACpF,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;AAC/C,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,kBAAkB;AAClB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AACvC,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AACpE,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;AAC/C,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,kBAAkB;AAClB,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AAC7C,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AACjF,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;AACrD,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;AAC/C,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,mBAAmB;AACnB,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;AACpD,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;AACxD,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,IAAI,MAAM,CAAC,CAAC;AAC/D,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;AAC/C,OAAO,CAAC,GAAG,EAAE,CAAC;AAEd,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC"} \ No newline at end of file diff --git a/tree-sitter/dsk/dsk-cli/tsconfig.json b/tree-sitter/dsk/dsk-cli/tsconfig.json new file mode 100644 index 0000000..e1599f7 --- /dev/null +++ b/tree-sitter/dsk/dsk-cli/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "allowJs": true, + + // Output configuration for CLI tool + "moduleResolution": "node", + "types": ["node"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "templates/**/*", + "dist/**/*", + "node_modules" + ] +} |