Skip to content
This repository was archived by the owner on Aug 25, 2025. It is now read-only.

Commit 566da96

Browse files
committed
Support for generating executables
fixes #34 fixes #35
1 parent bb805f1 commit 566da96

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

src/CodegenFile.php

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ final class CodegenFile {
4848
private ?ICodegenFormatter $formatter;
4949
private ?string $fileNamespace;
5050
private Map<string, ?string> $useNamespaces = Map {};
51+
private ?string $shebang;
52+
private ?string $pseudoMainHeader;
53+
private ?string $pseudoMainFooter;
5154

5255
public function __construct(
5356
private IHackCodegenConfig $config,
@@ -218,7 +221,6 @@ public function getFormatter(): ?ICodegenFormatter {
218221
return $this->formatter;
219222
}
220223

221-
222224
private function getFileTypeDeclaration(): string {
223225
switch($this->fileType) {
224226
case CodegenFileType::PHP:
@@ -232,9 +234,63 @@ private function getFileTypeDeclaration(): string {
232234
}
233235
}
234236

237+
/**
238+
* Useful when creating scripts.
239+
*
240+
* You probably want:
241+
* setShebangLine('#!/usr/bin/env hhvm')
242+
*/
243+
public function setShebangLine(string $shebang): this {
244+
invariant(
245+
!strpbrk($shebang, "\n"),
246+
"Expected single line",
247+
);
248+
invariant(
249+
Str::startsWith($shebang, '#!'),
250+
'Shebang lines start with #!',
251+
);
252+
$this->shebang = $shebang;
253+
return $this;
254+
}
255+
256+
/**
257+
* Use to execute code before declarations.
258+
*
259+
* Useful for scripts; eg:
260+
* setPseudoMainHeader('require_once("vendor/autoload.php");');
261+
*/
262+
public function setPseudoMainHeader(string $code): this {
263+
$this->pseudoMainHeader = $code;
264+
return $this;
265+
}
266+
267+
/**
268+
* Use to execute code after declarations.
269+
*
270+
* Useful for scripts; eg:
271+
* setPseudoMainFooter((new MyScript())->main($argv));
272+
*/
273+
public function setPseudoMainFooter(string $code): this {
274+
$this->pseudoMainFooter = $code;
275+
return $this;
276+
}
277+
278+
private function assertNotHackStrictForExecutable(): void {
279+
invariant(
280+
$this->fileType !== CodegenFileType::HACK_STRICT,
281+
"Hack Strict can't be used for executables",
282+
);
283+
}
284+
235285
public function render(): string {
236286
$builder = new HackBuilder($this->config);
237287

288+
$shebang = $this->shebang;
289+
if ($shebang !== null) {
290+
$this->assertNotHackStrictForExecutable();
291+
$builder->addLine($shebang);
292+
}
293+
238294
$builder->addLine($this->getFileTypeDeclaration());
239295
$header = $this->config->getFileHeader();
240296
if ($header) {
@@ -303,10 +359,20 @@ private function getContent(): string {
303359
'namespace %s;',
304360
$this->fileNamespace,
305361
);
362+
306363
foreach ($this->useNamespaces as $ns => $as) {
307364
$builder->addLine($as === null ? "use $ns;" : "use $ns as $as;");
308365
}
309366

367+
$header = $this->pseudoMainHeader;
368+
if ($header !== null) {
369+
$this->assertNotHackStrictForExecutable();
370+
$builder
371+
->ensureNewLine()
372+
->add($header)
373+
->ensureNewLine();
374+
}
375+
310376
foreach ($this->beforeTypes as $type) {
311377
$builder->ensureNewLine()->newLine();
312378
$builder->add($type->render());
@@ -329,6 +395,15 @@ private function getContent(): string {
329395
$builder->ensureNewLine()->newLine();
330396
$builder->add($type->render());
331397
}
398+
399+
$footer = $this->pseudoMainFooter;
400+
if ($footer !== null) {
401+
$this->assertNotHackStrictForExecutable();
402+
$builder
403+
->ensureEmptyLine()
404+
->add($footer)
405+
->ensureNewLine();
406+
}
332407
return $builder->getCode();
333408
}
334409

test/CodegenFileTest.codegen

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ class AllAutogenerated {
1717
}
1818
}
1919

20+
!@#$%codegentest:testExecutable
21+
#!/usr/bin/env hhvm
22+
<?hh
23+
// Codegen Tests
24+
/**
25+
* This file is generated. Do not modify it manually!
26+
*
27+
* @-generated SignedSource<<f3a2d86a759204d632f25dcf3d8731bb>>
28+
*/
29+
require_once('vendor/autoload.php');
30+
31+
function main(): void {
32+
print("Hello, world!\n");
33+
}
34+
35+
main();
36+
2037
!@#$%codegentest:testGenerateTopLevelFunctions
2138
<?hh
2239
// Codegen Tests

test/CodegenFileTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,57 @@ public function testPhpFile(): void {
281281
$this->assertUnchanged($code);
282282
}
283283

284+
public function testExecutable(): void {
285+
$cgf = $this->getCodegenFactory();
286+
$code = $cgf->codegenFile('no_file')
287+
->setFileType(CodegenFileType::HACK_PARTIAL)
288+
->setShebangLine('#!/usr/bin/env hhvm')
289+
->setPseudoMainHeader(
290+
'require_once(\'vendor/autoload.php\');'
291+
)
292+
->addFunction(
293+
$cgf->codegenFunction('main')
294+
->setReturnType('void')
295+
->setBody('print("Hello, world!\n");')
296+
)
297+
->setPseudoMainFooter('main();')
298+
->render();
299+
$this->assertUnchanged($code);
300+
}
301+
302+
public function testNoShebangInStrict(): void {
303+
$this->expectException(
304+
/* HH_FIXME[2049] no hhi for invariantexception */
305+
\HH\InvariantException::class,
306+
);
307+
$cgf = $this->getCodegenFactory();
308+
$code = $cgf->codegenFile('no_file')
309+
->setFileType(CodegenFileType::HACK_STRICT)
310+
->setShebangLine('#!/usr/bin/env hhvm')
311+
->render();
312+
}
313+
314+
public function testNoPseudoMainHeaderInStrict(): void {
315+
$this->expectException(
316+
/* HH_FIXME[2049] no hhi for invariantexception */
317+
\HH\InvariantException::class,
318+
);
319+
$cgf = $this->getCodegenFactory();
320+
$code = $cgf->codegenFile('no_file')
321+
->setFileType(CodegenFileType::HACK_STRICT)
322+
->setPseudoMainHeader('exit();')
323+
->render();
324+
}
325+
326+
public function testNoPseudoMainFooterInStrict(): void {
327+
$this->expectException(
328+
/* HH_FIXME[2049] no hhi for invariantexception */
329+
\HH\InvariantException::class,
330+
);
331+
$cgf = $this->getCodegenFactory();
332+
$code = $cgf->codegenFile('no_file')
333+
->setFileType(CodegenFileType::HACK_STRICT)
334+
->setPseudoMainFooter('exit();')
335+
->render();
336+
}
284337
}

0 commit comments

Comments
 (0)