LibJS: Implement "throw"

You can now throw an expression to the nearest catcher! :^)

To support throwing arbitrary values, I added an Exception class that
sits as a wrapper around whatever is thrown. In the future it will be
a logical place to store a call stack.
This commit is contained in:
Andreas Kling 2020-03-24 22:03:50 +01:00
parent db024a9cb1
commit faddf3a1db
Notes: sideshowbarker 2024-07-19 08:08:37 +09:00
13 changed files with 160 additions and 7 deletions

View File

@ -0,0 +1,5 @@
try {
throw 123;
} catch (e) {
console.log(e);
}

View File

@ -480,7 +480,7 @@ Value Identifier::execute(Interpreter& interpreter) const
{
auto value = interpreter.get_variable(string());
if (value.is_undefined())
return interpreter.throw_exception(interpreter.heap().allocate<Error>("ReferenceError", String::format("'%s' not known", string().characters())));
return interpreter.throw_exception<Error>("ReferenceError", String::format("'%s' not known", string().characters()));
return value;
}
@ -762,13 +762,19 @@ void CatchClause::dump(int indent) const
body().dump(indent + 1);
}
void ThrowStatement::dump(int indent) const
{
ASTNode::dump(indent);
argument().dump(indent + 1);
}
Value TryStatement::execute(Interpreter& interpreter) const
{
interpreter.run(block(), {}, ScopeType::Try);
if (auto* exception = interpreter.exception()) {
if (m_handler) {
interpreter.clear_exception();
Vector<Argument> arguments { { m_handler->parameter(), Value(exception) } };
Vector<Argument> arguments { { m_handler->parameter(), exception->value() } };
interpreter.run(m_handler->body(), move(arguments));
}
}
@ -786,4 +792,10 @@ Value CatchClause::execute(Interpreter&) const
return {};
}
Value ThrowStatement::execute(Interpreter& interrupt) const
{
auto value = m_argument->execute(interrupt);
return interrupt.throw_exception(value);
}
}

View File

@ -675,4 +675,22 @@ private:
RefPtr<BlockStatement> m_finalizer;
};
class ThrowStatement final : public Statement {
public:
explicit ThrowStatement(NonnullRefPtr<Expression> argument)
: m_argument(move(argument))
{
}
const Expression& argument() const { return m_argument; }
virtual void dump(int indent) const override;
virtual Value execute(Interpreter&) const override;
private:
virtual const char* class_name() const override { return "ThrowStatement"; }
NonnullRefPtr<Expression> m_argument;
};
}

View File

@ -32,6 +32,7 @@ class ASTNode;
class Argument;
class Cell;
class Error;
class Exception;
class Expression;
class Function;
class HandleImpl;

View File

@ -188,7 +188,7 @@ Value Interpreter::call(Function* function, Value this_value, const Vector<Value
return result;
}
Value Interpreter::throw_exception(Error* exception)
Value Interpreter::throw_exception(Exception* exception)
{
m_exception = exception;
unwind(ScopeType::Try);

View File

@ -26,12 +26,13 @@
#pragma once
#include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <AK/String.h>
#include <AK/FlyString.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Exception.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
@ -107,9 +108,20 @@ public:
Object* array_prototype() { return m_array_prototype; }
Object* error_prototype() { return m_error_prototype; }
Error* exception() { return m_exception; }
Exception* exception() { return m_exception; }
void clear_exception() { m_exception = nullptr; }
Value throw_exception(Error*);
template<typename T, typename... Args>
Value throw_exception(Args&&... args)
{
return throw_exception(heap().allocate<T>(forward<Args>(args)...));
}
Value throw_exception(Exception*);
Value throw_exception(Value value)
{
return throw_exception(heap().allocate<Exception>(value));
}
private:
Heap m_heap;
@ -123,7 +135,7 @@ private:
Object* m_array_prototype { nullptr };
Object* m_error_prototype { nullptr };
Error* m_exception { nullptr };
Exception* m_exception { nullptr };
ScopeType m_unwind_until { ScopeType::None };
};

View File

@ -62,6 +62,7 @@ Lexer::Lexer(StringView source)
s_keywords.set("null", TokenType::NullLiteral);
s_keywords.set("undefined", TokenType::UndefinedLiteral);
s_keywords.set("return", TokenType::Return);
s_keywords.set("throw", TokenType::Throw);
s_keywords.set("true", TokenType::BoolLiteral);
s_keywords.set("try", TokenType::Try);
s_keywords.set("typeof", TokenType::Typeof);

View File

@ -12,6 +12,7 @@ OBJS = \
Runtime/ConsoleObject.o \
Runtime/Error.o \
Runtime/ErrorPrototype.o \
Runtime/Exception.o \
Runtime/Function.o \
Runtime/GlobalObject.o \
Runtime/MathObject.o \

View File

@ -197,6 +197,8 @@ NonnullRefPtr<Statement> Parser::parse_statement()
return parse_for_statement();
case TokenType::If:
return parse_if_statement();
case TokenType::Throw:
return parse_throw_statement();
case TokenType::Try:
return parse_try_statement();
default:
@ -520,6 +522,12 @@ NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration()
return create_ast_node<VariableDeclaration>(create_ast_node<Identifier>(name), move(initializer), declaration_type);
}
NonnullRefPtr<ThrowStatement> Parser::parse_throw_statement()
{
consume(TokenType::Throw);
return create_ast_node<ThrowStatement>(parse_expression(0));
}
NonnullRefPtr<TryStatement> Parser::parse_try_statement()
{
consume(TokenType::Try);
@ -700,6 +708,7 @@ bool Parser::match_statement() const
|| type == TokenType::Delete
|| type == TokenType::Do
|| type == TokenType::If
|| type == TokenType::Throw
|| type == TokenType::Try
|| type == TokenType::While
|| type == TokenType::For

View File

@ -52,6 +52,7 @@ public:
NonnullRefPtr<VariableDeclaration> parse_variable_declaration();
NonnullRefPtr<ForStatement> parse_for_statement();
NonnullRefPtr<IfStatement> parse_if_statement();
NonnullRefPtr<ThrowStatement> parse_throw_statement();
NonnullRefPtr<TryStatement> parse_try_statement();
NonnullRefPtr<CatchClause> parse_catch_clause();

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibJS/Runtime/Exception.h>
namespace JS {
Exception::Exception(Value value)
: m_value(value)
{
}
Exception::~Exception()
{
}
void Exception::visit_children(Visitor& visitor)
{
Cell::visit_children(visitor);
visitor.visit(m_value);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <LibJS/Runtime/Cell.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
class Exception : public Cell {
public:
explicit Exception(Value);
virtual ~Exception() override;
Value value() const { return m_value; }
private:
virtual const char* class_name() const override { return "Exception"; }
virtual void visit_children(Visitor&) override;
Value m_value;
};
}

View File

@ -105,6 +105,7 @@ enum class TokenType {
Slash,
SlashEquals,
StringLiteral,
Throw,
Tilde,
Try,
Typeof,