petcarescript
Version:
PetCareScript - A modern, expressive programming language designed for humans
460 lines (363 loc) โข 12.4 kB
JavaScript
/**
* PetCareScript CLI
* Command Line Interface
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const PetCareScript = require('../src/petcarescript');
class PetCareScriptCLI {
constructor() {
this.pcs = new PetCareScript();
this.version = '1.0.0';
}
run() {
const args = process.argv.slice(2);
if (args.length === 0) {
this.startRepl();
} else {
this.handleArguments(args);
}
}
handleArguments(args) {
const command = args[0];
switch (command) {
case '--version':
case '-v':
console.log(`PetCareScript v${this.version}`);
break;
case '--help':
case '-h':
this.showHelp();
break;
case 'init':
this.initProject(args[1]);
break;
case 'run':
this.runFile(args[1]);
break;
case 'repl':
this.startRepl();
break;
case 'compile':
this.compileFile(args[1], args[2]);
break;
case 'test':
this.runTests(args[1]);
break;
case 'check':
this.checkSyntax(args[1]);
break;
case 'format':
this.formatFile(args[1]);
break;
default:
if (command.endsWith('.pcs')) {
this.runFile(command);
} else {
console.error(`Unknown command: ${command}`);
console.error('Use --help to see available commands.');
process.exit(1);
}
break;
}
}
showHelp() {
console.log(`
PetCareScript v${this.version}
A modern and expressive programming language
USAGE:
pcs [command] [options]
COMMANDS:
<file.pcs> Execute a PetCareScript file
run <file.pcs> Execute a PetCareScript file
repl Start interactive REPL
init [name] Create a new PetCareScript project
compile <src> <dest> Compile to JavaScript
test [folder] Run tests
check <file.pcs> Check syntax without executing
format <file.pcs> Format PetCareScript file
OPTIONS:
-h, --help Show this help
-v, --version Show version
EXAMPLES:
pcs myfile.pcs # Execute myfile.pcs
pcs repl # Start REPL
pcs init myapp # Create new project
pcs test # Run all tests
`);
}
runFile(filename) {
if (!filename) {
console.error('Error: Filename is required');
process.exit(1);
}
if (!fs.existsSync(filename)) {
console.error(`Error: File '${filename}' not found`);
process.exit(1);
}
console.log(`๐ Executing ${filename}...`);
const result = this.pcs.runFile(filename);
if (!result.success) {
console.error(`โ Error: ${result.error}`);
process.exit(1);
}
console.log('โ
Execution completed successfully');
}
checkSyntax(filename) {
if (!filename) {
console.error('Error: Filename is required');
process.exit(1);
}
if (!fs.existsSync(filename)) {
console.error(`Error: File '${filename}' not found`);
process.exit(1);
}
try {
const source = fs.readFileSync(filename, 'utf8');
const Tokenizer = require('../src/lexer/tokenizer');
const Parser = require('../src/parser/parser');
// Try to tokenize and parse
const tokenizer = new Tokenizer(source);
const tokens = tokenizer.tokenize();
const parser = new Parser(tokens);
const ast = parser.parse();
console.log(`โ
Syntax check passed for ${filename}`);
} catch (error) {
console.error(`โ Syntax error in ${filename}: ${error.message}`);
process.exit(1);
}
}
formatFile(filename) {
console.log(`๐จ Formatting ${filename}...`);
console.log('โ ๏ธ Formatter not yet implemented');
// TODO: Implement code formatter
}
startRepl() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'pcs> '
});
console.log(`PetCareScript REPL v${this.version}`);
console.log('Type "exit", "quit" or Ctrl+C to quit.');
console.log('Type "help" for available commands.\n');
rl.prompt();
rl.on('line', (line) => {
const input = line.trim();
if (input === 'exit' || input === 'quit') {
rl.close();
return;
}
if (input === 'help') {
this.showReplHelp();
rl.prompt();
return;
}
if (input === 'clear' || input === 'cls') {
console.clear();
rl.prompt();
return;
}
if (input === 'version') {
console.log(`PetCareScript v${this.version}`);
rl.prompt();
return;
}
if (input === '') {
rl.prompt();
return;
}
try {
const result = this.pcs.run(input);
if (!result.success) {
console.error(`โ ${result.error}`);
}
} catch (error) {
console.error(`โ ${error.message}`);
}
rl.prompt();
});
rl.on('close', () => {
console.log('\n๐ Goodbye!');
process.exit(0);
});
rl.on('SIGINT', () => {
console.log('\n๐ Goodbye!');
process.exit(0);
});
}
showReplHelp() {
console.log(`
REPL COMMANDS:
help Show this help
clear, cls Clear screen
version Show version
exit, quit Exit REPL
EXAMPLES:
store x = 42; # Define a variable
show x; # Print the value
build hello() { show "Hello!"; } # Define function
hello(); # Call function
`);
}
initProject(projectName = 'my-pcs-project') {
const projectDir = path.resolve(projectName);
if (fs.existsSync(projectDir)) {
console.error(`Error: Directory '${projectName}' already exists`);
process.exit(1);
}
console.log(`๐ Creating project '${projectName}'...`);
// Create project structure
fs.mkdirSync(projectDir);
fs.mkdirSync(path.join(projectDir, 'src'));
fs.mkdirSync(path.join(projectDir, 'tests'));
// Main file
const mainFile = `// ${projectName}
// Main PetCareScript project file
build main() {
show "Hello, PetCareScript!";
show "Project: ${projectName}";
store message = "Welcome to your new project!";
show message;
}
main();
`;
fs.writeFileSync(path.join(projectDir, 'src', 'main.pcs'), mainFile);
// Package.json
const packageJson = {
name: projectName,
version: '1.0.0',
description: `PetCareScript project: ${projectName}`,
main: 'src/main.pcs',
scripts: {
start: 'pcs src/main.pcs',
test: 'pcs test tests/'
},
dependencies: {
petcarescript: `^${this.version}`
}
};
fs.writeFileSync(
path.join(projectDir, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
// README
const readme = `# ${projectName}
Project created with PetCareScript.
## Run
\`\`\`bash
npm start
# or
pcs src/main.pcs
\`\`\`
## Structure
- \`src/\` - Source code
- \`tests/\` - Tests
- \`package.json\` - Project configuration
## Documentation
Visit [petcarescript.org](https://petcarescript.org) for complete documentation.
`;
fs.writeFileSync(path.join(projectDir, 'README.md'), readme);
// Test file
const testFile = `// Tests for ${projectName}
build testBasic() {
show "Running basic test...";
store result = 2 + 2;
when (result == 4) {
show "โ
Test passed: 2 + 2 = 4";
} otherwise {
show "โ Test failed";
}
}
testBasic();
`;
fs.writeFileSync(path.join(projectDir, 'tests', 'test.pcs'), testFile);
console.log(`โ
Project '${projectName}' created successfully!`);
console.log(`\n๐ Next steps:`);
console.log(` cd ${projectName}`);
console.log(` pcs src/main.pcs`);
}
compileFile(srcFile, destFile) {
if (!srcFile) {
console.error('Error: Source file is required');
process.exit(1);
}
if (!destFile) {
destFile = srcFile.replace('.pcs', '.js');
}
console.log(`๐ Compiling ${srcFile} -> ${destFile}...`);
try {
const source = fs.readFileSync(srcFile, 'utf8');
// Basic conversion to JavaScript
let jsCode = this.transpileToJS(source);
fs.writeFileSync(destFile, jsCode);
console.log(`โ
Compilation completed: ${destFile}`);
} catch (error) {
console.error(`โ Compilation error: ${error.message}`);
process.exit(1);
}
}
transpileToJS(pcsCode) {
// Basic PetCareScript -> JavaScript conversion
let jsCode = pcsCode;
// Simple substitutions
jsCode = jsCode.replace(/store /g, 'let ');
jsCode = jsCode.replace(/build /g, 'function ');
jsCode = jsCode.replace(/when \(/g, 'if (');
jsCode = jsCode.replace(/otherwise/g, 'else');
jsCode = jsCode.replace(/repeat \(/g, 'while (');
jsCode = jsCode.replace(/loop \(/g, 'for (');
jsCode = jsCode.replace(/give /g, 'return ');
jsCode = jsCode.replace(/show /g, 'console.log');
jsCode = jsCode.replace(/yes/g, 'true');
jsCode = jsCode.replace(/no/g, 'false');
jsCode = jsCode.replace(/empty/g, 'null');
jsCode = jsCode.replace(/also /g, '&& ');
jsCode = jsCode.replace(/either /g, '|| ');
jsCode = jsCode.replace(/blueprint /g, 'class ');
jsCode = jsCode.replace(/self\./g, 'this.');
return `// Generated from PetCareScript
// Do not edit this file directly
${jsCode}
`;
}
runTests(testDir = './tests') {
console.log(`๐งช Running tests in ${testDir}...`);
if (!fs.existsSync(testDir)) {
console.error(`Error: Test directory '${testDir}' not found`);
process.exit(1);
}
const testFiles = fs.readdirSync(testDir)
.filter(file => file.endsWith('.pcs'))
.map(file => path.join(testDir, file));
if (testFiles.length === 0) {
console.log('โ ๏ธ No test files found');
return;
}
let passed = 0;
let failed = 0;
for (const testFile of testFiles) {
console.log(`\n๐ Running ${path.basename(testFile)}...`);
const result = this.pcs.runFile(testFile);
if (result.success) {
passed++;
console.log(`โ
${path.basename(testFile)} passed`);
} else {
failed++;
console.log(`โ ${path.basename(testFile)} failed: ${result.error}`);
}
}
console.log(`\n๐ Results:`);
console.log(` โ
Passed: ${passed}`);
console.log(` โ Failed: ${failed}`);
console.log(` ๐ Total: ${testFiles.length}`);
if (failed > 0) {
process.exit(1);
}
}
}
// Execute CLI
const cli = new PetCareScriptCLI();
cli.run();