Selaa lähdekoodia

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.
Andreas Kling 5 vuotta sitten
vanhempi
commit
faddf3a1db

+ 5 - 0
Base/home/anon/js/throw.js

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

+ 14 - 2
Libraries/LibJS/AST.cpp

@@ -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);
+}
+
 }

+ 18 - 0
Libraries/LibJS/AST.h

@@ -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;
+};
+
 }

+ 1 - 0
Libraries/LibJS/Forward.h

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

+ 1 - 1
Libraries/LibJS/Interpreter.cpp

@@ -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);

+ 16 - 4
Libraries/LibJS/Interpreter.h

@@ -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 };
 };

+ 1 - 0
Libraries/LibJS/Lexer.cpp

@@ -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);

+ 1 - 0
Libraries/LibJS/Makefile

@@ -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 \

+ 9 - 0
Libraries/LibJS/Parser.cpp

@@ -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

+ 1 - 0
Libraries/LibJS/Parser.h

@@ -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();
 

+ 46 - 0
Libraries/LibJS/Runtime/Exception.cpp

@@ -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);
+}
+
+}

+ 46 - 0
Libraries/LibJS/Runtime/Exception.h

@@ -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;
+};
+
+}

+ 1 - 0
Libraries/LibJS/Token.h

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