forked from brownplt/pyret-npm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pyret.js
executable file
·280 lines (249 loc) · 11.4 KB
/
pyret.js
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#!/usr/bin/env node
const usage = require('command-line-usage');
const commandLineArgs = require('command-line-args');
const pyretClient = require('./client-lib');
const path = require('path');
const stripAnsi = require('strip-ansi');
const {version} = require("./package.json");
const compilerPath = path.join(__dirname, "pyret-lang", "build", "phaseA", "pyret.jarr");
const usages = [
{
header: `Pyret Command-line Interface v${version}`,
content:
`The {bold pyret} command compiles and runs Pyret programs. It helps manage a compile server that runs in the background to speed up compilation jobs, and manages state in a project's working directory to cache compiled files.`
},
{
header: 'Basic Usage',
content: [
//'$ pyret --init',
//"# Creates a {underline .pyret/} directory in the current directory and",
//"# starts a compile server (if one isn't already running)",
' $ cat ahoy-world.arr',
' check: "Ahoy " + "world!" is "Ahoy world!" end',
' $ pyret {underline ahoy-world.arr}',
' Starting Parley server...',
' 1/1 modules compiled',
' Looks shipshape, your test passed, mate!',
'',
'',
' This command compiled and ran {underline ahoy-world.arr}. The first time, this will take a few seconds as a server starts up in the background, in order to make future compiles fast.',
'',
'',
' It\'s worth noting that the file is compiled into a standalone JavaScript file with the {underline .jarr} extension:',
'',
' $ node {underline ahoy-world.jarr}',
' Looks shipshape, your test passed, mate!',
'',
'',
' Most uses (e.g. for homework) only need to use the {bold pyret} command directly on {underline .arr} files, but there are several other options that can be provided.',
]
},
{
header: 'Options',
optionList: [
{
name: 'help',
alias: 'h',
description: 'Show this help message.'
},
{
name: 'version',
alias: 'v',
description: "Print version information"
},
/*
{
name: 'init',
alias: 'i',
type: Boolean,
description: "Performs two convenient setup tasks: creates a {underline .pyret/} directory in the current directory, and starts a server if one isn't running for this user already."
},
*/
{
name: 'program',
alias: 'p',
typeLabel: "{underline <file>.arr}",
defaultOption: true,
description: "This is the default option, so using the flag is optional. Specifies the path to the program to compile (usually a .arr file). Will start a server if one isn't running, and will report an error if there is no {underline .pyret} in this directory or in any parent of this directory. Generates a standalone compiled file based on {bold --outfile}, and immediately executes it. The exit code is non-zero if the file fails to compile, and is the exit code of the executed program if it compiles successfully."
},
{
name: 'outfile',
alias: 'o',
typeLabel: "{underline <file>.jarr}",
description: "Specify the file to put the standalone compiled output into. The program can be re-run without re-compiling by using the {bold node} command. Defaults to the name of the {bold --program} with {underline .arr} replaced with {underline .jarr}."
},
{
name: 'quiet',
alias: 'q',
description: "Don't show the progress indicator output like \"1/4 modules compiled\""
},
{
name: 'perilous',
description: "Compromises error semantics for speed. If the program has no errors, it will produce the same outputs and answers. Currently, this means eliding most annotation checks in compiled code and in libraries. Fine-grained control is intentionally not provided since the specifics of how this works may change, but in general it will do less error checking in exchange for faster execution, and not change the meaning of programs with no errors."
},
{
name: 'type-check',
alias: 'y',
description: "Turn on the type-checker, and report errors found by the type checker as compilation errors."
},
{
name: 'checks',
alias: 'e',
description: "Specify which checks to execute (all, none, or main) (default all)"
},
{
name: 'no-check-mode',
alias: 'k',
description: "Omit check blocks during compilation, and generate a standalone program that doesn't print testing information at all."
},
/*
{
name: 'clean',
alias: 'n',
type: Boolean,
description: "Removes all compiled code in the {underline .pyret} directory. This can be necessary when Pyret updates to clear out stale compiled files."
},
*/
{
name: 'shutdown',
alias: 's',
type: Boolean,
description: "Shuts down the currently-running compile server (if any is running), by sending it a message over the specified or default {bold --port}"
},
{
name: 'port',
alias: 't',
type: String,
description: "Specify the path to a socket file to use to communicate with the server. Defaults to {underline /tmp/parley-<username>/comm.sock}."
},
]
},
{
header: 'The .pyret/ Directory',
content: [
'The first time you run the {bold pyret} command in a directory, it creates a {underline .pyret/} directory there.',
'',
'This directory is used to store the compiled versions of individual {underline .arr} files in your project. They will appear in {underline .pyret/compiled}.',
'',
'Each directory in which you run {bold pyret} will have this sub-directory created. In general, you should never need to look in or modify the directory. If you want to look at the innards of Pyret, you can check out these files.'
]
},
{
header: 'Multiple and Support Files',
content: [
`If you've used code.pyret.org for a class, you may have support code that looks like`,
"",
"import shared-gdrive('something.arr', '<long-id-here>') as S",
"",
`You may also have imports with 'my-gdrive' for your own work.`,
"",
`These types of import aren't supported in the offline compiler. You can,
however, get the source for these files, save them, and use 'file'
imports to access the contents.`,
"",
"import file('something.arr') as S",
"import file('my-tests.arr') as T",
"",
`The paths you use should be relative to the Pyret file that contains the
import statements.`
]
},
{
header: 'The Compile Server',
content: [
`The compiler will not run without a running server. The {bold pyret} command
tries to connect to a compile server on the specified port on
startup, and if it cannot, starts one before continuing. The server
accepts requests to start and stop compile jobs, and to shut down, and
sends messages indicating compile status and when the job is complete.`.replace("\n", ""),
'',
`The default mode of operation is to have a single compile server running
per user (hence the default naming of the {bold --port}). This makes it
simple to change directories to different projects and get the benefits
of the server's quick responses. If multiple compile jobs are sent at the
same time, they are queued and processed in FIFO order.`.replace("\n", ""),
'',
`This server is experimental, and likely can get stuck running, or lose
track of its connection, so it's worth specifying what resources it uses
so they can be cleaned up. The server that's started in the background
does the work of creating the specified socket file, and its process
should clean up that file when it's done or when {bold --shutdown} is
used. You can also manually remove it (check
{underline /tmp/parley-<username>/}) if something gets wedged. This is
the only bit of state that the client and server use to communicate. The
server runs a file called {underline pyret.jarr}, so if you want to
check on potential runaway server processes this command can find them:`,
'',
'$ {bold ps} aux | {bold grep} {underline pyret.jarr}'
]
},
{
header: 'Support and Contact',
content: 'This interface is experimental. Feedback and issue reports at https://github.com/brownplt/pyret-lang/issues/new are most appreciated.'
}
];
const optionDefinitions = [
{ name: 'help', alias: 'h', type: Boolean, group: 'meta', defaultValue: false },
{ name: 'version', alias: 'v', type: Boolean, group: 'meta', defaultValue: false },
{ name: 'quiet', alias: 'q', type: Boolean, group: 'meta' },
{ name: 'norun', alias: 'c', type: Boolean, group: "meta", defaultValue: false },
// These options affect how the client starts up and communiates with the server
{ name: 'shutdown', alias: 's', type: Boolean, group: "client", defaultValue: false },
{ name: 'port', alias: 't', type: String, group: "client" },
// { name: 'clean', type: Boolean, group: "client", defaultValue: false },
{ name: 'compiler', type: String, defaultValue: compilerPath, group: "client" },
{ name: 'global-parley', type: String, defaultValue: "~/.parley/" },
{ name: 'local-parley', type: String, defaultValue: ".pyret" },
{ name: 'program', alias: 'p', type: String, group: "pyret-options", defaultOption: true },
// These options are passed on to the compiler, and have no effect (yet)
// on the client
{ name: 'base-dir', type: String, group: "pyret-options" },
{ name: 'outfile', alias: 'o', type: String, group: "pyret-options" },
{ name: 'require-config', type: String, group: "pyret-options" },
{ name: 'builtin-js-dir', type: String, multiple: true, group: "pyret-options" },
{ name: 'builtin-arr-dir', type: String, multiple: true, group: "pyret-options" },
{ name: 'allow-builtin-overrides', type: Boolean, group: "pyret-options", defaultValue: false },
{ name: 'checks', defaultValue: 'all', alias: 'e', type: String, group: "pyret-options" },
{ name: 'no-check-mode', alias: 'k', type: Boolean, group: "pyret-options", defaultValue: false },
{ name: 'compiled-dir', type: String, group: "pyret-options" },
{ name: 'standalone-file', type: String, group: "pyret-options" },
{ name: 'deps-file', type: String, group: "pyret-options" },
{ name: 'perilous', type: Boolean, group: "pyret-options", defaultValue: false },
{ name: 'type-check', alias: 'y', type: Boolean, group: "pyret-options", defaultValue: false },
];
let options;
// https://stackoverflow.com/a/20585886/2718315
function printUsage() {
if(process.stdout.isTTY) {
console.log(usage(usages));
}
else {
console.log(stripAnsi(usage(usages)));
}
}
function printVersion() {
console.log(`Pyret Command-line Interface v${version}`);
}
try {
options = commandLineArgs(optionDefinitions);
if(options.meta.help) {
printUsage();
process.exit(0);
}
else if(options.meta.version) {
printVersion();
process.exit(0);
}
}
catch(e) {
printUsage();
process.exit(1);
}
// Default behavior: use ".jarr" to replace ".arr"
if(!options["pyret-options"]["outfile"] && options["pyret-options"]["program"]) {
const programName = options["pyret-options"]["program"];
if(path.extname(programName) === ".arr") {
options["pyret-options"]["outfile"] = programName.slice(0, -4) + ".jarr";
}
}
pyretClient.start(options);