Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
508 changes: 459 additions & 49 deletions lib/argparse.ts

Large diffs are not rendered by default.

149 changes: 48 additions & 101 deletions src/chad-native.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { compileNative, setSkipSemanticAnalysis, setEmitLLVMOnly, setTargetCpu } from './native-compiler-lib.js';
import { getDtsContent } from './codegen/stdlib/embedded-dts.js';
import { ArgumentParser } from '../lib/argparse.js';

declare const fs: {
existsSync(filename: string): boolean;
Expand All @@ -23,54 +24,27 @@ declare const child_process: {

const VERSION = '0.1.0';

function printHelp(): void {
console.log('chad - compile TypeScript to native binaries via LLVM');
console.log('');
console.log('Usage: chad <command> [options] <file>');
console.log('');
console.log('Commands:');
console.log(' build <file> Compile to a native binary');
console.log(' run <file> Compile and run');
console.log(' ir <file> Emit LLVM IR only');
console.log(' init Generate starter project (chadscript.d.ts, tsconfig.json, hello.ts)');
console.log(' clean Remove the .build directory');
console.log('');
console.log('Options:');
console.log(' -o <output> Specify output file');
console.log(' --skip-semantic-analysis Skip semantic analysis');
console.log(' --target-cpu=CPU Set LLVM target CPU (default: native)');
console.log(' -h, --help Show this help message');
console.log(' --version Show version');
console.log('');
console.log('Examples:');
console.log(' chad build hello.ts');
console.log(' chad build hello.ts -o myapp');
console.log(' chad run hello.ts');
console.log(' chad ir hello.ts');
}
const parser = new ArgumentParser('chad', 'compile TypeScript to native binaries via LLVM');
parser.addSubcommand('build', 'Compile to a native binary');
parser.addSubcommand('run', 'Compile and run');
parser.addSubcommand('ir', 'Emit LLVM IR only');
parser.addSubcommand('init', 'Generate starter project');
parser.addSubcommand('clean', 'Remove the .build directory');

function printVersion(): void {
console.log('chad ' + VERSION);
}
parser.addFlag('version', '', 'Show version');
parser.addScopedOption('output', 'o', 'Specify output file', '', 'build,run,ir');
parser.addScopedFlag('skip-semantic-analysis', '', 'Skip semantic analysis', 'build,run,ir');
parser.addScopedOption('target-cpu', '', 'Set LLVM target CPU', 'native', 'build,run,ir');
parser.addPositional('input', 'Input .ts or .js file');

const args = process.argv;
parser.parse(process.argv);

if (args.length < 1) {
printHelp();
if (parser.getFlag('version')) {
console.log('chad ' + VERSION);
process.exit(0);
}

const command = args[0];

if (command === '-h' || command === '--help') {
printHelp();
process.exit(0);
}

if (command === '--version') {
printVersion();
process.exit(0);
}
const command = parser.getSubcommand();

if (command === 'init') {
const dtsContent = getDtsContent();
Expand Down Expand Up @@ -107,83 +81,49 @@ if (command === 'clean') {
process.exit(0);
}

if (command !== 'build' && command !== 'run' && command !== 'ir' && command !== 'init') {
const endsWithTs = command.substr(command.length - 3) === '.ts';
const endsWithJs = command.substr(command.length - 3) === '.js';
if (endsWithTs || endsWithJs) {
console.log('chad: error: missing command. did you mean chad build ' + command + '?');
} else {
console.log('chad: error: unknown command ' + command);
}
console.log('Run chad --help for usage');
process.exit(1);
if (command.length === 0) {
parser.printHelp();
process.exit(0);
}

let inputFile: string | null = null;
let outputFile: string | null = null;
let argIdx = 1;
while (argIdx < args.length) {
const arg = args[argIdx];
if (arg === '-h' || arg === '--help') {
printHelp();
process.exit(0);
} else if (arg === '--version') {
printVersion();
process.exit(0);
} else if (arg === '--skip-semantic-analysis') {
setSkipSemanticAnalysis(true);
argIdx = argIdx + 1;
} else if (arg.substr(0, 13) === '--target-cpu=') {
setTargetCpu(arg.substr(13));
argIdx = argIdx + 1;
} else if (arg === '-o') {
argIdx = argIdx + 1;
if (argIdx < args.length) {
outputFile = args[argIdx];
argIdx = argIdx + 1;
}
} else if (arg.substr(0, 1) === '-') {
console.log('chad: error: unknown option ' + arg);
console.log('Run chad --help for usage');
process.exit(1);
} else if (inputFile === null) {
inputFile = arg;
argIdx = argIdx + 1;
} else {
argIdx = argIdx + 1;
}
if (parser.getFlag('skip-semantic-analysis')) {
setSkipSemanticAnalysis(true);
}

const cpuOpt = parser.getOption('target-cpu');
if (cpuOpt.length > 0) {
setTargetCpu(cpuOpt);
}

if (inputFile === null) {
const inputFile = parser.getPositional(0);
if (inputFile.length === 0) {
console.log('chad: error: no input files');
console.log('Usage: chad ' + command + ' [options] <input.ts>');
process.exit(1);
throw new Error('unreachable');
}

let theInputFile: string = '';
theInputFile = inputFile;

if (!fs.existsSync(theInputFile)) {
console.log('chad: error: file not found: ' + theInputFile);
if (!fs.existsSync(inputFile)) {
console.log('chad: error: file not found: ' + inputFile);
process.exit(1);
throw new Error('unreachable');
}

let inputForOutput: string = theInputFile;
let inputForOutput: string = inputFile;
if (inputForOutput.substr(0, 1) === '/') {
inputForOutput = path.basename(inputForOutput);
}
let theOutputFile: string = '.build/' + inputForOutput;
if (outputFile !== null) {
theOutputFile = outputFile;
let outputFile: string = '.build/' + inputForOutput;
const explicitOutput = parser.getOption('output');
if (explicitOutput.length > 0) {
outputFile = explicitOutput;
} else if (inputForOutput.substr(inputForOutput.length - 3) === '.ts') {
theOutputFile = '.build/' + inputForOutput.substr(0, inputForOutput.length - 3);
outputFile = '.build/' + inputForOutput.substr(0, inputForOutput.length - 3);
} else if (inputForOutput.substr(inputForOutput.length - 3) === '.js') {
theOutputFile = '.build/' + inputForOutput.substr(0, inputForOutput.length - 3);
outputFile = '.build/' + inputForOutput.substr(0, inputForOutput.length - 3);
}

const outputDir = path.dirname(theOutputFile);
const outputDir = path.dirname(outputFile);
if (!fs.existsSync(outputDir)) {
child_process.execSync('mkdir -p ' + outputDir);
}
Expand All @@ -192,13 +132,20 @@ if (command === 'ir') {
setEmitLLVMOnly(true);
}

compileNative(theInputFile, theOutputFile);
compileNative(inputFile, outputFile);

if (command === 'run') {
const binPath = path.resolve(theOutputFile);
const binPath = path.resolve(outputFile);
if (!fs.existsSync(binPath)) {
console.log('chad: error: compilation produced no binary');
process.exit(1);
}
child_process.execSync(binPath);
const rest = parser.getRestArgs();
let runCmd = binPath;
let ri = 0;
while (ri < rest.length) {
runCmd = runCmd + ' ' + rest[ri];
ri = ri + 1;
}
child_process.execSync(runCmd);
}
24 changes: 24 additions & 0 deletions tests/fixtures/argparse/double-dash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ArgumentParser } from '../../../lib/argparse.js';

const parser = new ArgumentParser('myapp', 'test double dash');
parser.addSubcommand('run', 'Run the project');
parser.addFlag('verbose', 'v', 'Enable verbose output');
parser.addPositional('input', 'Input file');

parser.parse(process.argv);

const cmd = parser.getSubcommand();
console.log('cmd:' + cmd);

const inp = parser.getPositional(0);
console.log('input:' + inp);

const rest = parser.getRestArgs();
let i = 0;
while (i < rest.length) {
console.log('rest:' + rest[i]);
i = i + 1;
}

console.log('TEST_PASSED');
process.exit(0);
26 changes: 26 additions & 0 deletions tests/fixtures/argparse/equals-syntax.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ArgumentParser } from '../../../lib/argparse.js';

const parser = new ArgumentParser('myapp', 'test equals syntax');
parser.addSubcommand('build', 'Build the project');
parser.addOption('target-cpu', '', 'Set target CPU', 'native');
parser.addOption('output', 'o', 'Output file', '');
parser.addPositional('input', 'Input file');

parser.parse(process.argv);

const cmd = parser.getSubcommand();
console.log('cmd:' + cmd);

const cpu = parser.getOption('target-cpu');
console.log('cpu:' + cpu);

const out = parser.getOption('output');
if (out.length > 0) {
console.log('output:' + out);
}

const inp = parser.getPositional(0);
console.log('input:' + inp);

console.log('TEST_PASSED');
process.exit(0);
37 changes: 37 additions & 0 deletions tests/fixtures/argparse/subcommand-basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ArgumentParser } from '../../../lib/argparse.js';

const parser = new ArgumentParser('myapp', 'test subcommands');
parser.addSubcommand('build', 'Build the project');
parser.addSubcommand('run', 'Run the project');
parser.addSubcommand('clean', 'Clean build artifacts');

parser.addFlag('verbose', 'v', 'Enable verbose output');
parser.addScopedOption('output', 'o', 'Output file', '', 'build,run');
parser.addPositional('input', 'Input file');

parser.parse(process.argv);

const cmd = parser.getSubcommand();
if (cmd === 'build') {
const inp = parser.getPositional(0);
const out = parser.getOption('output');
if (out.length > 0) {
console.log('build:' + inp + ':' + out);
} else {
console.log('build:' + inp);
}
} else if (cmd === 'run') {
const inp = parser.getPositional(0);
console.log('run:' + inp);
} else if (cmd === 'clean') {
console.log('clean');
} else {
console.log('none');
}

if (parser.getFlag('verbose')) {
console.log('verbose');
}

console.log('TEST_PASSED');
process.exit(0);
2 changes: 1 addition & 1 deletion tests/network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ testSocket();
});

await new Promise<void>((resolve) => {
server.listen(9876, '127.0.0.1', resolve);
server.listen(0, '127.0.0.1', resolve);
});

try {
Expand Down
21 changes: 21 additions & 0 deletions tests/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,4 +803,25 @@ export const testCases: TestCase[] = [
expectTestPassed: true,
description: 'Uint8Array constructor, index read/write, and .length'
},
{
name: 'argparse-subcommand-basic',
fixture: 'tests/fixtures/argparse/subcommand-basic.ts',
expectTestPassed: true,
description: 'ArgumentParser subcommand dispatch with flags and options',
args: ['build', 'hello.ts', '-o', 'out']
},
{
name: 'argparse-double-dash',
fixture: 'tests/fixtures/argparse/double-dash.ts',
expectTestPassed: true,
description: 'ArgumentParser -- separator puts remaining args into restArgs',
args: ['run', 'hello.ts', '--', 'arg1', 'arg2']
},
{
name: 'argparse-equals-syntax',
fixture: 'tests/fixtures/argparse/equals-syntax.ts',
expectTestPassed: true,
description: 'ArgumentParser --key=value option syntax',
args: ['--target-cpu=x86-64', 'build', 'hello.ts']
},
];
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
"moduleResolution": "node"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests", "src/chadc-native.ts"]
"exclude": ["node_modules", "dist", "tests", "src/chadc-native.ts", "src/chad-native.ts"]
}
Loading