1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 07:48:11 +00:00

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
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()); auto value = interpreter.get_variable(string());
if (value.is_undefined()) 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; return value;
} }
@ -762,13 +762,19 @@ void CatchClause::dump(int indent) const
body().dump(indent + 1); body().dump(indent + 1);
} }
void ThrowStatement::dump(int indent) const
{
ASTNode::dump(indent);
argument().dump(indent + 1);
}
Value TryStatement::execute(Interpreter& interpreter) const Value TryStatement::execute(Interpreter& interpreter) const
{ {
interpreter.run(block(), {}, ScopeType::Try); interpreter.run(block(), {}, ScopeType::Try);
if (auto* exception = interpreter.exception()) { if (auto* exception = interpreter.exception()) {
if (m_handler) { if (m_handler) {
interpreter.clear_exception(); 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)); interpreter.run(m_handler->body(), move(arguments));
} }
} }
@ -786,4 +792,10 @@ Value CatchClause::execute(Interpreter&) const
return {}; 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; 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 Argument;
class Cell; class Cell;
class Error; class Error;
class Exception;
class Expression; class Expression;
class Function; class Function;
class HandleImpl; class HandleImpl;

View file

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

View file

@ -26,12 +26,13 @@
#pragma once #pragma once
#include <AK/FlyString.h>
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/FlyString.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Heap/Heap.h> #include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Exception.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
namespace JS { namespace JS {
@ -107,9 +108,20 @@ public:
Object* array_prototype() { return m_array_prototype; } Object* array_prototype() { return m_array_prototype; }
Object* error_prototype() { return m_error_prototype; } Object* error_prototype() { return m_error_prototype; }
Error* exception() { return m_exception; } Exception* exception() { return m_exception; }
void clear_exception() { m_exception = nullptr; } 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: private:
Heap m_heap; Heap m_heap;
@ -123,7 +135,7 @@ private:
Object* m_array_prototype { nullptr }; Object* m_array_prototype { nullptr };
Object* m_error_prototype { nullptr }; Object* m_error_prototype { nullptr };
Error* m_exception { nullptr }; Exception* m_exception { nullptr };
ScopeType m_unwind_until { ScopeType::None }; 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("null", TokenType::NullLiteral);
s_keywords.set("undefined", TokenType::UndefinedLiteral); s_keywords.set("undefined", TokenType::UndefinedLiteral);
s_keywords.set("return", TokenType::Return); s_keywords.set("return", TokenType::Return);
s_keywords.set("throw", TokenType::Throw);
s_keywords.set("true", TokenType::BoolLiteral); s_keywords.set("true", TokenType::BoolLiteral);
s_keywords.set("try", TokenType::Try); s_keywords.set("try", TokenType::Try);
s_keywords.set("typeof", TokenType::Typeof); s_keywords.set("typeof", TokenType::Typeof);

View file

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

View file

@ -197,6 +197,8 @@ NonnullRefPtr<Statement> Parser::parse_statement()
return parse_for_statement(); return parse_for_statement();
case TokenType::If: case TokenType::If:
return parse_if_statement(); return parse_if_statement();
case TokenType::Throw:
return parse_throw_statement();
case TokenType::Try: case TokenType::Try:
return parse_try_statement(); return parse_try_statement();
default: 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); 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() NonnullRefPtr<TryStatement> Parser::parse_try_statement()
{ {
consume(TokenType::Try); consume(TokenType::Try);
@ -700,6 +708,7 @@ bool Parser::match_statement() const
|| type == TokenType::Delete || type == TokenType::Delete
|| type == TokenType::Do || type == TokenType::Do
|| type == TokenType::If || type == TokenType::If
|| type == TokenType::Throw
|| type == TokenType::Try || type == TokenType::Try
|| type == TokenType::While || type == TokenType::While
|| type == TokenType::For || type == TokenType::For

View file

@ -52,6 +52,7 @@ public:
NonnullRefPtr<VariableDeclaration> parse_variable_declaration(); NonnullRefPtr<VariableDeclaration> parse_variable_declaration();
NonnullRefPtr<ForStatement> parse_for_statement(); NonnullRefPtr<ForStatement> parse_for_statement();
NonnullRefPtr<IfStatement> parse_if_statement(); NonnullRefPtr<IfStatement> parse_if_statement();
NonnullRefPtr<ThrowStatement> parse_throw_statement();
NonnullRefPtr<TryStatement> parse_try_statement(); NonnullRefPtr<TryStatement> parse_try_statement();
NonnullRefPtr<CatchClause> parse_catch_clause(); 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, Slash,
SlashEquals, SlashEquals,
StringLiteral, StringLiteral,
Throw,
Tilde, Tilde,
Try, Try,
Typeof, Typeof,