Repository navigation
Expand file tree
/
Copy pathcli.ts
More file actions
249 lines (221 loc) · 9.93 KB
/
Copy pathcli.ts
File metadata and controls
249 lines (221 loc) · 9.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import { Command } from 'commander';
import { doQuery } from './actions/query';
import { doInferGrammar, doVerboseCheck } from './actions/inferGrammar';
import fs from 'fs';
import assert from 'assert';
import { resolveRPC } from './actions/rpc';
import { configure } from './env';
import { doInferAdapter } from './actions/inferAdapter';
import { QueryNames } from './rules/queries/mapping';
import { ExitAndLogStats, Stats } from './actions/utils';
import { evaluateAdapter } from './actions/evaluateAdapter';
function checkFileExists(filePath: string | undefined) {
if (filePath === undefined) return; // We don't care if it's undefined
if (!fs.existsSync(filePath)) {
console.error(`File '${filePath}' does not exist`);
process.exit(1);
}
}
const cli = new Command();
cli.name('ulangs-toolkit');
cli.description('Universal Language Server Toolkit');
cli.version('0.1.0');
export type CLIQueryArguments = {
adapter: string | undefined;
query: string;
ignoreSchema: boolean;
};
cli.command('list-queries')
.description('List all available queries')
.action(() => {
console.log('Available queries:');
for (const queryName of QueryNames()) {
console.log(`- ${queryName}`);
}
});
cli.command('query')
.description('Generates Prolog from file with a given query')
.argument('<target>', 'File to generate to parse and analyze')
.argument('<lexer>', 'Lexer (path) file to use')
.argument('<parser>', 'Parser (path) file to use')
.option('-a, --adapter <adapter>', 'Adapter (path) file to use')
.option('-q, --query <query>', 'Query to run after generating facts', "printAST")
.option('-I, --ignore-schema', 'Ignore schema validation', false)
.action(async (target: string, lexer: string, parser: string, options: CLIQueryArguments) => {
assert(fs.existsSync(target), 'Target file does not exist');
assert(fs.existsSync(lexer), 'Lexer file does not exist');
assert(fs.existsSync(parser), 'Parser file does not exist');
if (options.adapter) {
checkFileExists(options.adapter);
}
if (options.query) {
// If the query ends with .pl, remove it
if (options.query.endsWith('.pl')) {
options.query = options.query.slice(0, -3);
}
// Check if the query exists
if (!(QueryNames().includes(options.query))) {
console.error(`Query '${options.query}' not found`);
process.exit(1);
}
}
doQuery(target, lexer, parser, options);
});
cli.command('check')
.description('Checks if source files can be parsed by given grammar')
.argument('<directory>', 'Directory to check')
.argument('<extension>', 'File extension to check, ex: .pyl')
.argument('<lexer>', 'Lexer file to use')
.argument('<parser>', 'Parser file to use')
.action(async (directory: string, extension: string, lexer: string, parser: string) => {
doVerboseCheck(directory, extension, lexer, parser);
});
export type CLIInferGrammarArguments = {
recursive: boolean;
initialLexer: string | undefined;
initialParser: string | undefined;
skipFirstGuess: boolean;
};
cli.command('infer-grammar')
.description('Infer grammar from a directory of files')
.argument('<directory>', 'Directory to infer grammar from')
.argument('<extension>', 'File extension to infer grammar from, ex: .pyl')
.argument('[outputDir]', 'Output directory for generated files', process.cwd())
.option('-iL, --initial-lexer <path>', 'Use a file as a starting point for the lexer', undefined)
.option('-iP, --initial-parser <path>', 'Use a file as a starting point for the parser', undefined)
.option('-R, --recursive', 'Detect grammar from subdirectories', false)
.option('-S, --skip-first-guess', 'Skips the first guess and starts from the previous intermediate solution. Useful when inital grammar is almost correct.', false)
// .option('-s, --skip-first-guess', 'Skips the first guess and starts from the previous intermediate solution. Useful when inital grammar is almost correct.', false)
.action(async (directory: string, extension: string, outputDir: string, options: CLIInferGrammarArguments) => {
// Check if the directory exists
if (!fs.existsSync(directory)) {
console.error(`Directory ${directory} does not exist`);
process.exit(1);
}
// Exit if extension does not start with a dot
if (!extension.startsWith('.')) {
console.error(`Extension ${extension} does not start with a dot`);
process.exit(1);
}
// Check inital grammar files exist
checkFileExists(options.initialLexer);
checkFileExists(options.initialParser);
// Check if the output directory exists
if (!fs.existsSync(outputDir)) {
console.error(`Output directory '${outputDir}' does not exist`);
process.exit(1);
}
doInferGrammar(directory, extension, outputDir, options);
});
export type CLIInferAdapterArguments = {
recursive: boolean;
skipFirstGuess: boolean;
lexer: string;
parser: string;
outputDir: string;
initialAdapter: string | undefined;
minimumJudgeScore: string;
};
cli.command('infer-adapter')
.description('Infer semantic adapter')
.argument('<directory>', 'Directory to infer adapter from')
.argument('<extension>', 'File extension to infer adapter from, ex: .pyl')
.argument('[outputDir]', 'Output directory for generated files', process.cwd())
.option('-L, --lexer <path>', 'Lexer file to use')
.option('-P, --parser <path>', 'Parser file to use')
.option('-iA, --initial-adapter <path>', 'Use a file as a starting point for the adapter', undefined)
.option('-R, --recursive', 'Detect grammar from subdirectories', false)
.option('-S, --skip-first-guess', 'Skips the first guess and starts from the previous intermediate solution. Useful when inital grammar is almost correct.', false)
.option('-J, --minimum-judge-score <score>', 'Minimum score to accept a judge score (default: 70)', '70')
// .option('-s, --skip-first-guess', 'Skips the first guess and starts from the previous intermediate solution. Useful when inital grammar is almost correct.', false)
.action(async (directory: string, extension: string, outputDir: string, options: CLIInferAdapterArguments) => {
// Check if the directory exists
if (!fs.existsSync(directory)) {
console.error(`Directory ${directory} does not exist`);
process.exit(1);
return;
}
// Exit if extension does not start with a dot
if (!extension.startsWith('.')) {
console.error(`Extension ${extension} does not start with a dot`);
process.exit(1);
return;
}
if (options.skipFirstGuess) throw new Error("Not implemented");
// Check grammar files exist
checkFileExists(options.lexer);
checkFileExists(options.parser);
// Check if the output directory exists
if (!fs.existsSync(outputDir)) {
console.error(`Output directory '${outputDir}' does not exist`);
process.exit(1);
return;
}
// Check if the initial adapter file exists
if (options.initialAdapter) {
checkFileExists(options.initialAdapter);
}
doInferAdapter(directory, extension, outputDir, options);
});
cli.command('rpc')
.description('Remote procedure call')
.argument('<function>', 'Function to call')
.argument('[payload]', 'Payload to send (can be piped)')
.action(async (functionName: string, payload?: string) => {
// Check if we're receiving input from a pipe
if (process.stdin.isTTY === undefined) {
// Reading from pipe
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
data += chunk;
});
process.stdin.on('end', () => {
resolveRPC(functionName, data.trim());
});
} else {
// Using command line argument
if (!payload) {
console.error('Error: Payload is required (when not piping input)');
process.exit(1);
}
resolveRPC(functionName, payload);
}
});
cli.command('evaluate-adapter')
.description('Evaluate a Prolog adapter against all code files in a directory')
.argument('<directory>', 'Directory containing code files to test')
.argument('<extension>', 'File extension to filter by, e.g. .lua')
.option('-A, --adapter <path>', 'Adapter file to use')
.option('-L, --lexer <path>', 'Lexer file to use')
.option('-P, --parser <path>', 'Parser file to use')
.option('-R, --recursive', 'Detect code files in subdirectories', false)
.action(async (directory, extension, options) => {
// Validate inputs
const { adapter, lexer, parser, recursive } = options;
if (!adapter || !fs.existsSync(adapter)) {
console.error(`Adapter file not found: ${adapter}`);
process.exit(1);
}
if (!lexer || !fs.existsSync(lexer)) {
console.error(`Lexer file not found: ${lexer}`);
process.exit(1);
}
if (!parser || !fs.existsSync(parser)) {
console.error(`Parser file not found: ${parser}`);
process.exit(1);
}
if (!fs.existsSync(directory)) {
console.error(`Directory not found: ${directory}`);
process.exit(1);
}
// Run evaluation
await evaluateAdapter(adapter, lexer, parser, directory, extension, recursive);
});
// Handle SIGINT (Ctrl+C) to gracefully exit
process.on('SIGINT', function () {
console.log("\nStopping... Goodbye!");
ExitAndLogStats(1);
});
configure(); // Configure environment variables
cli.parse();