Skip to content

Commit

Permalink
Merge pull request #201 from phel-lang/improved-repl-line-numbers
Browse files Browse the repository at this point in the history
Improved REPL line numbers and tested for CTRL-D and error messages
  • Loading branch information
Chemaclass committed Jan 27, 2021
2 parents 73a198d + cb74f12 commit 47a0526
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 42 deletions.
15 changes: 6 additions & 9 deletions src/php/Command/ReplCommand.php
Expand Up @@ -32,7 +32,7 @@ final class ReplCommand

/** @var string[] */
private array $inputBuffer = [];
private int $statementNumber = 1;
private int $lineNumber = 1;

public function __construct(
ReplCommandIoInterface $io,
Expand Down Expand Up @@ -84,18 +84,21 @@ private function addLineFromPromptToBuffer(): void

$isInitialInput = empty($this->inputBuffer);
$prompt = $isInitialInput ? self::INITIAL_PROMPT : self::OPEN_PROMPT;
$input = $this->io->readline(sprintf($prompt, $this->statementNumber));
$input = $this->io->readline(sprintf($prompt, $this->lineNumber));

if ($this->io->isBracketedPasteSupported()) {
$this->io->write(self::DISABLE_BRACKETED_PASTE);
}

$this->lineNumber++;

if ($input === null && $isInitialInput) {
// Ctrl+D will exit the repl
$this->inputBuffer[] = self::EXIT_REPL;
} elseif ($input === null && !$isInitialInput) {
// Ctrl+D will empty the buffer
$this->inputBuffer = [];
$this->io->writeln();
} else {
$this->inputBuffer[] = $input;
}
Expand All @@ -119,21 +122,15 @@ private function analyzeInputBuffer(): void
return;
}

if ('' === end($this->inputBuffer)) {
array_pop($this->inputBuffer);
return;
}

$fullInput = implode(PHP_EOL, $this->inputBuffer);

try {
$result = $this->compiler->eval($fullInput, $this->statementNumber);
$result = $this->compiler->eval($fullInput, $this->lineNumber - count($this->inputBuffer));

$this->io->writeln($this->printer->print($result));
$this->io->addHistory($fullInput);

$this->inputBuffer = [];
$this->statementNumber++;
} catch (UnfinishedParserException $e) {
// The input is valid but more input is missing to finish the parsing.
} catch (CompilerException $e) {
Expand Down
11 changes: 8 additions & 3 deletions src/php/Exceptions/TextExceptionPrinter.php
Expand Up @@ -60,17 +60,22 @@ public function getExceptionString(PhelCodeException $e, CodeSnippet $codeSnippe
$errorStartLocation = $e->getStartLocation() ?? $codeSnippet->getStartLocation();
$errorEndLocation = $e->getEndLocation() ?? $codeSnippet->getEndLocation();
$errorFirstLine = $errorStartLocation->getLine();
$codeFirstLine = $codeSnippet->getStartLocation()->getLine();

$str .= $this->style->blue($e->getMessage()) . PHP_EOL;
$str .= 'in ' . $errorStartLocation->getFile() . ':' . $errorFirstLine . PHP_EOL . PHP_EOL;

$lines = explode(PHP_EOL, $codeSnippet->getCode());
$endLineLength = strlen((string)$codeSnippet->getEndLocation()->getLine());
$padLength = $endLineLength - strlen((string)$codeSnippet->getStartLocation()->getLine());
$padLength = $endLineLength - strlen((string)$codeFirstLine);

foreach ($lines as $index => $line) {
$str .= str_pad((string)($errorFirstLine + $index), $padLength, ' ', STR_PAD_LEFT);
$str .= '| ' . $line . PHP_EOL;
$str .= str_pad((string)($codeFirstLine + $index), $padLength, ' ', STR_PAD_LEFT);
if (strlen($line) > 0) {
$str .= '| ' . $line . PHP_EOL;
} else {
$str .= '|' . PHP_EOL;
}

$eStartLine = $errorStartLocation->getLine();
if ($eStartLine === $errorEndLocation->getLine()
Expand Down
@@ -0,0 +1,5 @@
phel:1> (php/+ 1
....:2> 2
....:3> <CTRL-D>
phel:4> (php/+ 1 2)
3
4 changes: 2 additions & 2 deletions tests/php/Integration/Repl/Fixtures/simple-add-multiline.test
@@ -1,4 +1,4 @@
phel:1> (php/+ 1
....:1> 2
....:1> )
....:2> 2
....:3> )
3
9 changes: 9 additions & 0 deletions tests/php/Integration/Repl/Fixtures/undefined-symbol.test
@@ -0,0 +1,9 @@
phel:1> (def my-fn (fn []
....:2> (undefined-fn 1 2)))
Can not resolve symbol 'undefined-fn'
in string:2

1| (def my-fn (fn []
2| (undefined-fn 1 2)))
^^^^^^^^^^^^

@@ -0,0 +1,14 @@
phel:1> (php/+ 1
....:2>
....:3> 2
....:4>
....:5> ]
Unterminated list (BRACKETS)
in string:5

1| (php/+ 1
2|
3| 2
4|
5| ]
^
@@ -1,11 +1,10 @@
phel:1> (php/+ 1
....:1> 2
....:1> ]
....:2> 2
....:3> ]
Unterminated list (BRACKETS)
in string:3

3| (php/+ 1
4| 2
5| ]
1| (php/+ 1
2| 2
3| ]
^

32 changes: 32 additions & 0 deletions tests/php/Integration/Repl/InputLine.php
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace PhelTest\Integration\Repl;

final class InputLine
{
private string $prompt;
private string $content;

public function __construct(string $prompt, string $content)
{
$this->prompt = $prompt;
$this->content = $content;
}

public function getContent(): string
{
return $this->content;
}

public function isCtrlD(): bool
{
return $this->content === '<CTRL-D>';
}

public function __toString(): string
{
return $this->prompt . $this->content;
}
}
30 changes: 11 additions & 19 deletions tests/php/Integration/Repl/ReplIntegrationTest.php
Expand Up @@ -26,16 +26,16 @@ final class ReplIntegrationTest extends TestCase
/**
* @dataProvider providerIntegration
*/
public function testIntegration(array $inputs, string $expectedOutput): void
public function testIntegration(string $expectedOutput, InputLine ...$inputs): void
{
$io = new ReplTestIo();
$repl = $this->createReplCommand($io);

$io->setInputs($inputs);
$io->setInputs(...$inputs);
$repl->run();
$replOutput = $io->getOutputString();

self::assertEquals(($expectedOutput), $replOutput);
self::assertEquals(trim($expectedOutput), trim($replOutput));
}

public function providerIntegration(): Generator
Expand All @@ -53,12 +53,12 @@ public function providerIntegration(): Generator
continue;
}

$filename = str_replace($fixturesDir . '/', '', $file->getRealPath());
$fileContent = file_get_contents($file->getRealpath());
$filename = str_replace($fixturesDir . '/', '', $file->getRealPath());

yield $filename => [
$this->getInputs($fileContent),
$this->filterExpectedOutputFromFileContent($fileContent),
$fileContent,
...$this->getInputs($fileContent),
];
}
}
Expand Down Expand Up @@ -88,29 +88,21 @@ private function createReplCommand(ReplTestIo $io): ReplCommand
}

/**
* @return list<string>
* @return InputLine[]
*/
private function getInputs(string $fileContent): array
{
$inputs = [];

foreach (explode(PHP_EOL, $fileContent) as $line) {
preg_match('/....:\d>(?<phel_code>.+)/', $line, $out);
preg_match('/(?<prompt>....:\d> ?)(?<phel_code>.+)?/', $line, $out);
if (!empty($out)) {
$inputs[] = trim($out['phel_code']);
$prompt = $out['prompt'];
$code = $out['phel_code'] ?? '';
$inputs[] = new InputLine($prompt, $code);
}
}

return $inputs;
}

private function filterExpectedOutputFromFileContent(string $fileContent): string
{
$outputFromFileContent = preg_replace('/....:\d>(.+)/', 'delete_me', $fileContent);

return implode(PHP_EOL, array_filter(
explode(PHP_EOL, $outputFromFileContent),
static fn (string $line): bool => $line !== 'delete_me'
));
}
}
18 changes: 15 additions & 3 deletions tests/php/Integration/Repl/ReplTestIo.php
Expand Up @@ -8,8 +8,12 @@

final class ReplTestIo implements ReplCommandIoInterface
{
/** @var array string[] */
private array $outputs = [];

/** @var InputLine[] */
private array $inputs = [];

private int $currentIndex = 0;

public function readHistory(): void
Expand All @@ -23,10 +27,15 @@ public function addHistory(string $line): void
public function readline(?string $prompt = null): ?string
{
if ($this->currentIndex < count($this->inputs)) {
$line = $this->inputs[$this->currentIndex];
$inputLine = $this->inputs[$this->currentIndex];
$this->writeln($inputLine->__toString() . PHP_EOL);
$this->currentIndex++;

return $line;
if ($inputLine->isCtrlD()) {
return null;
}

return $inputLine->getContent();
}

return null;
Expand All @@ -42,12 +51,15 @@ public function writeln(string $string = ''): void
$this->outputs[] = $string;
}

public function setInputs(array $inputs): void
public function setInputs(InputLine ...$inputs): void
{
$this->inputs = $inputs;
$this->currentIndex = 0;
}

/**
* @return string[]
*/
public function getOutputs(): array
{
return array_slice($this->outputs, 2, -1);
Expand Down

0 comments on commit 47a0526

Please sign in to comment.