Skip to content

Commit

Permalink
Fixed error handling for wrong string escape sequences.
Browse files Browse the repository at this point in the history
  • Loading branch information
smuuf committed Dec 20, 2024
1 parent 62f6147 commit 7514756
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 39 deletions.
24 changes: 24 additions & 0 deletions src/Ex/WrongEscapeSequenceException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Smuuf\Primi\Ex;

/**
* StringEscaping can throw this.
*
* @internal
*/
class WrongEscapeSequenceException extends EngineException {

/**
* @param string $piece Which character was tried to be used as part of
* escape sequence? For example "č" in "\č".
*/
public function __construct(
public readonly string $char,
) {
parent::__construct("Unrecognized string escape sequence '{$char}'");
}

}
42 changes: 25 additions & 17 deletions src/Handlers/Kinds/FStringLiteral.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Smuuf\Primi\Helpers\Interned;
use Smuuf\Primi\Helpers\StringEscaping;
use Smuuf\Primi\Compiler\Compiler;
use Smuuf\Primi\Ex\InternalSyntaxError;
use Smuuf\Primi\Ex\WrongEscapeSequenceException;
use Smuuf\Primi\Handlers\Handler;

class FStringLiteral extends Handler {
Expand All @@ -25,30 +27,36 @@ public static function reduce(array &$node): void {
// 2. FStringExpr nodes as node array.
// ... which will be easy to handle during compilation

$parts = [];
foreach (Func::ensure_indexed($node['core']['parts']) as $part) {
try {

// Extract "just text" from the non-expr nodes, so we don't have
// to deal with them later.
if ($part['name'] === 'FStringTxt') {
$parts = [];
foreach (Func::ensure_indexed($node['core']['parts']) as $part) {

// Convert double-curly-braces in ordinary text subnodes
// to single ones.
$part = \str_replace(['{{', '}}'], ['{', '}'], $part['text']);
$part = StringEscaping::unescapeString($part);
$parts[] = $part;
// Extract "just text" from the non-expr nodes, so we don't have
// to deal with them later.
if ($part['name'] === 'FStringTxt') {

} elseif ($part['name'] === 'FStringExpr') {
// Convert double-curly-braces in ordinary text subnodes
// to single ones.
$part = \str_replace(['{{', '}}'], ['{', '}'], $part['text']);
$part = StringEscaping::unescapeString($part);
$parts[] = $part;

// Keep f-string's nested-expression nodes as-is.
$parts[] = $part;
} elseif ($part['name'] === 'FStringExpr') {

// Keep f-string's nested-expression nodes as-is.
$parts[] = $part;

} else {
throw new EngineInternalError(
"Unknown node type encountered when parsing f-string"
);
}

} else {
throw new EngineInternalError(
"Unknown node type encountered when parsing f-string"
);
}

} catch (WrongEscapeSequenceException $exc) {
throw InternalSyntaxError::fromNode($node, $exc->getMessage());
}

$node['parts'] = $parts;
Expand Down
13 changes: 11 additions & 2 deletions src/Handlers/Kinds/StringLiteral.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
namespace Smuuf\Primi\Handlers\Kinds;

use Smuuf\Primi\VM\Machine;
use Smuuf\Primi\Compiler\Compiler;
use Smuuf\Primi\Ex\InternalSyntaxError;
use Smuuf\Primi\Ex\WrongEscapeSequenceException;
use Smuuf\Primi\Helpers\Interned;
use Smuuf\Primi\Helpers\StringEscaping;
use Smuuf\Primi\Compiler\Compiler;
use Smuuf\Primi\Handlers\Handler;

class StringLiteral extends Handler {
Expand All @@ -17,7 +19,14 @@ class StringLiteral extends Handler {
*/
public static function reduce(array &$node): void {

$node['text'] = StringEscaping::unescapeString($node['core']['text']);
try {
$node['text'] = StringEscaping::unescapeString(
$node['core']['text'],
);
} catch (WrongEscapeSequenceException $exc) {
throw InternalSyntaxError::fromNode($node, $exc->getMessage());
}

unset($node['quote']);
unset($node['core']);

Expand Down
7 changes: 3 additions & 4 deletions src/Helpers/StringEscaping.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Smuuf\Primi\Helpers;

use Smuuf\Primi\Ex\InternalSyntaxError;
use Smuuf\Primi\Ex\WrongEscapeSequenceException;
use Smuuf\Primi\Stdlib\StaticExceptionTypes;
use Smuuf\StrictObject;

Expand Down Expand Up @@ -51,10 +53,7 @@ public static function unescapeString(string $str): string {

// The backslashed character doesn't represent any known escape
// sequence, therefore error.
Exceptions::piggyback(
StaticExceptionTypes::getRuntimeErrorType(),
"Unrecognized string escape sequence '{$m[0]}'.",
);
throw new WrongEscapeSequenceException($m[0]);

}, $str);

Expand Down
30 changes: 14 additions & 16 deletions src/VM/Machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Smuuf\Primi\Helpers\Exceptions;
use Smuuf\Primi\ScopeComposite;
use Smuuf\Primi\Structures\CallArgs;
use Smuuf\Primi\Values\ExceptionValue;

class Machine {

Expand Down Expand Up @@ -396,24 +397,23 @@ public function run(Frame $frame): AbstractValue {
unset($a, $b);
goto vm_check_exc;
}
$vStack->push($b);
$vStack->push($b); // @phpstan-ignore-line
unset($a, $b);
break;

case Machine::OP_CALL_FUNCTION_EX:
// Pop 1: Kwargs dict.
// Pop 2: Args list.
// Pop 3: Callable object.
/**
* @var AbstractValue $a
* @var AbstractValue $b
* @var AbstractValue $c
*/
/** @var AbstractValue */
$a = $vStack->pop();
/** @var AbstractValue */
$b = $vStack->pop();
/** @var AbstractValue */
$c = $vStack->pop();
$frame->storeOpIndex($opIndex);
$scope->setVariables($locals);
$c = $vStack->pop()->invoke(
$c = $c->invoke(
$ctx,
new CallArgs(
$b->getCoreValue(),
Expand All @@ -424,19 +424,21 @@ public function run(Frame $frame): AbstractValue {
unset($a, $b, $c);
goto vm_check_exc;
}
$vStack->push($c);
$vStack->push($c); // @phpstan-ignore-line
unset($a, $b, $c);
break;

case Machine::OP_CALL_FUNCTION_N:
/** @var AbstractValue */
$a = $vStack->pop();
$frame->storeOpIndex($opIndex);
$scope->setVariables($locals);
$a = $vStack->pop()->invoke($ctx);
$a = $a->invoke($ctx);
if ($ctx->getPendingException()) {
unset($a);
goto vm_check_exc;
}
$vStack->push($a);
$vStack->push($a); // @phpstan-ignore-line
unset($a);
break;

Expand All @@ -463,7 +465,6 @@ public function run(Frame $frame): AbstractValue {
// Arg #1: A list array of key-value pairs, for example:
// [['key', 'value'], ...]
$vStack->push(new DictValue($op[1]));
unset($a, $b, $i, $pairs);
break;

case Machine::OP_BUILD_DICT:
Expand Down Expand Up @@ -635,15 +636,14 @@ public function run(Frame $frame): AbstractValue {
$op[2],
$op[3],
$ctx,
$scope,
$builtins,
);
$vStack->push($a);
unset($a);
break;

case Machine::OP_EXC_THROW:
// Pop #1: ExceptionValue.
/** @var ExceptionValue */
$a = $vStack->pop();
$frame->storeOpIndex($opIndex);
if (Types::isSubtypeOf($a->getType(), StaticExceptionTypes::getBaseExceptionType())) {
Expand All @@ -655,17 +655,15 @@ public function run(Frame $frame): AbstractValue {
"Only exceptions can be thrown",
);
}
goto vm_check_exc;
unset($a);
break;
goto vm_check_exc;

case Machine::OP_IMPORT:
// Arg #1: Dotpath of the module to be imported.
// Arg #2: List of names to import.
$scope->setVariables($locals);
ImportStatement::handleImport($ctx, $op[1], $op[2]);
goto vm_check_exc;
break;

case Machine::OP_RETURN:
goto vm_loop_exit;
Expand Down

0 comments on commit 7514756

Please sign in to comment.