From f5476be702009968468731df5e23cdeb68fdb6e0 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 7 Mar 2020 19:42:11 +0100 Subject: [PATCH] LibJS: Start building a JavaScript engine for SerenityOS :^) I always tell people to start building things by working on the thing that seems the most interesting right now. The most interesting thing here was an AST + simple interpreter, so that's where we start! There is no lexer or parser yet, we build an AST directly and then execute it in the interpreter, producing a return value. This seems like the start of something interesting. :^) --- Libraries/LibJS/AST.cpp | 144 ++++++++++++++++++++++++ Libraries/LibJS/AST.h | 188 ++++++++++++++++++++++++++++++++ Libraries/LibJS/Forward.h | 37 +++++++ Libraries/LibJS/Function.cpp | 42 +++++++ Libraries/LibJS/Function.h | 52 +++++++++ Libraries/LibJS/Interpreter.cpp | 72 ++++++++++++ Libraries/LibJS/Interpreter.h | 59 ++++++++++ Libraries/LibJS/Makefile | 15 +++ Libraries/LibJS/Object.cpp | 43 ++++++++ Libraries/LibJS/Object.h | 50 +++++++++ Libraries/LibJS/Value.cpp | 56 ++++++++++ Libraries/LibJS/Value.h | 139 +++++++++++++++++++++++ Userland/Makefile | 2 +- Userland/js.cpp | 59 ++++++++++ 14 files changed, 957 insertions(+), 1 deletion(-) create mode 100644 Libraries/LibJS/AST.cpp create mode 100644 Libraries/LibJS/AST.h create mode 100644 Libraries/LibJS/Forward.h create mode 100644 Libraries/LibJS/Function.cpp create mode 100644 Libraries/LibJS/Function.h create mode 100644 Libraries/LibJS/Interpreter.cpp create mode 100644 Libraries/LibJS/Interpreter.h create mode 100644 Libraries/LibJS/Makefile create mode 100644 Libraries/LibJS/Object.cpp create mode 100644 Libraries/LibJS/Object.h create mode 100644 Libraries/LibJS/Value.cpp create mode 100644 Libraries/LibJS/Value.h create mode 100644 Userland/js.cpp diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp new file mode 100644 index 0000000000..06e5849187 --- /dev/null +++ b/Libraries/LibJS/AST.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include + +namespace JS { + +Value ScopeNode::execute(Interpreter& interpreter) const +{ + return interpreter.run(*this); +} + +Value FunctionDeclaration::execute(Interpreter& interpreter) const +{ + auto* function = new Function(name(), body()); + interpreter.global_object().put(m_name, Value(function)); + return Value(function); +} + +Value CallExpression::execute(Interpreter& interpreter) const +{ + auto callee = interpreter.global_object().get(name()); + ASSERT(callee.is_object()); + auto* callee_object = callee.as_object(); + ASSERT(callee_object->is_function()); + auto& function = static_cast(*callee_object); + return interpreter.run(function.body()); +} + +Value ReturnStatement::execute(Interpreter& interpreter) const +{ + auto value = argument().execute(interpreter); + interpreter.do_return(); + return value; +} + +Value add(Value lhs, Value rhs) +{ + ASSERT(lhs.is_number()); + ASSERT(rhs.is_number()); + return Value(lhs.as_double() + rhs.as_double()); +} + +Value sub(Value lhs, Value rhs) +{ + ASSERT(lhs.is_number()); + ASSERT(rhs.is_number()); + return Value(lhs.as_double() - rhs.as_double()); +} + +Value BinaryExpression::execute(Interpreter& interpreter) const +{ + auto lhs_result = m_lhs->execute(interpreter); + auto rhs_result = m_rhs->execute(interpreter); + + switch (m_op) { + case BinaryOp::Plus: + return add(lhs_result, rhs_result); + case BinaryOp::Minus: + return sub(lhs_result, rhs_result); + } + + ASSERT_NOT_REACHED(); +} + +static void print_indent(int indent) +{ + for (int i = 0; i < indent * 2; ++i) + putchar(' '); +} + +void ASTNode::dump(int indent) const +{ + print_indent(indent); + printf("%s\n", class_name()); +} + +void ScopeNode::dump(int indent) const +{ + ASTNode::dump(indent); + for (auto& child : children()) + child.dump(indent + 1); +} + +void BinaryExpression::dump(int indent) const +{ + ASTNode::dump(indent); + m_lhs->dump(indent + 1); + m_rhs->dump(indent + 1); +} + +void CallExpression::dump(int indent) const +{ + print_indent(indent); + printf("%s '%s'\n", class_name(), name().characters()); +} + +void Literal::dump(int indent) const +{ + print_indent(indent); + printf("%d\n", (i32)m_value.as_double()); +} + +void FunctionDeclaration::dump(int indent) const +{ + print_indent(indent); + printf("%s '%s'\n", class_name(), name().characters()); + body().dump(indent + 1); +} + +void ReturnStatement::dump(int indent) const +{ + ASTNode::dump(indent); + argument().dump(indent + 1); +} + +} diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h new file mode 100644 index 0000000000..7c1ca77866 --- /dev/null +++ b/Libraries/LibJS/AST.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS { + +class ASTNode { +public: + virtual ~ASTNode() {} + virtual const char* class_name() const = 0; + virtual Value execute(Interpreter&) const = 0; + virtual void dump(int indent) const; + +protected: + ASTNode() {} + +private: +}; + +class ScopeNode : public ASTNode { +public: + template + T& append(Args&&... args) + { + auto child = make(forward(args)...); + m_children.append(move(child)); + return static_cast(m_children.last()); + } + + const NonnullOwnPtrVector& children() const { return m_children; } + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + +protected: + ScopeNode() {} + +private: + NonnullOwnPtrVector m_children; +}; + +class Program : public ScopeNode { +public: + Program() {} + +private: + virtual const char* class_name() const override { return "Program"; } +}; + +class BlockStatement : public ScopeNode { +public: + BlockStatement() {} + +private: + virtual const char* class_name() const override { return "BlockStatement"; } +}; + +class FunctionDeclaration : public ASTNode { +public: + FunctionDeclaration(String name, NonnullOwnPtr body) + : m_name(move(name)) + , m_body(move(body)) + { + } + + String name() const { return m_name; } + const ScopeNode& body() const { return *m_body; } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "FunctionDeclaration"; } + + String m_name; + NonnullOwnPtr m_body; +}; + +class Expression : public ASTNode { +public: +}; + +class ReturnStatement : public ASTNode { +public: + explicit ReturnStatement(NonnullOwnPtr argument) + : m_argument(move(argument)) + { + } + + const Expression& argument() const { return *m_argument; } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "ReturnStatement"; } + + NonnullOwnPtr m_argument; +}; + +enum class BinaryOp { + Plus, + Minus, +}; + +class BinaryExpression : public Expression { +public: + BinaryExpression(BinaryOp op, NonnullOwnPtr lhs, NonnullOwnPtr rhs) + : m_op(op) + , m_lhs(move(lhs)) + , m_rhs(move(rhs)) + { + } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "BinaryExpression"; } + + BinaryOp m_op; + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; +}; + +class Literal : public Expression { +public: + explicit Literal(Value value) + : m_value(move(value)) + { + } + + virtual Value execute(Interpreter&) const override { return m_value; } + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "Literal"; } + + Value m_value; +}; + +class CallExpression : public Expression { +public: + explicit CallExpression(String name) + : m_name(move(name)) + { + } + + virtual Value execute(Interpreter&) const override; + virtual void dump(int indent) const override; + + const String& name() const { return m_name; } + +private: + virtual const char* class_name() const override { return "CallExpression"; } + + String m_name; +}; + +} diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h new file mode 100644 index 0000000000..d7ca20d622 --- /dev/null +++ b/Libraries/LibJS/Forward.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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. + */ + +#pragma once + +namespace JS { + +class ASTNode; +class Interpreter; +class Object; +class ScopeNode; +class Value; + +} diff --git a/Libraries/LibJS/Function.cpp b/Libraries/LibJS/Function.cpp new file mode 100644 index 0000000000..d0d26b1514 --- /dev/null +++ b/Libraries/LibJS/Function.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include + +namespace JS { + +Function::Function(String name, const ScopeNode& body) + : m_name(move(name)) + , m_body(body) +{ +} + +Function::~Function() +{ +} + +} diff --git a/Libraries/LibJS/Function.h b/Libraries/LibJS/Function.h new file mode 100644 index 0000000000..5e119bc98a --- /dev/null +++ b/Libraries/LibJS/Function.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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. + */ + +#pragma once + +#include +#include + +namespace JS { + +class Function : public Object { +public: + Function(String name, const ScopeNode& body); + virtual ~Function(); + + const String& name() const { return m_name; } + const ScopeNode& body() const { return m_body; } + +protected: + virtual const char* class_name() const override { return "Function"; } + +private: + virtual bool is_function() const final { return true; } + + String m_name; + const ScopeNode& m_body; +}; + +} diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp new file mode 100644 index 0000000000..aabac0771a --- /dev/null +++ b/Libraries/LibJS/Interpreter.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include + +namespace JS { + +Interpreter::Interpreter() +{ + m_global_object = new Object; +} + +Interpreter::~Interpreter() +{ +} + +Value Interpreter::run(const ScopeNode& scope_node) +{ + enter_scope(scope_node); + + Value last_value = js_undefined(); + for (auto& node : scope_node.children()) { + last_value = node.execute(*this); + } + + exit_scope(scope_node); + return last_value; +} + +void Interpreter::enter_scope(const ScopeNode& scope_node) +{ + m_scope_stack.append({ scope_node }); +} + +void Interpreter::exit_scope(const ScopeNode& scope_node) +{ + ASSERT(&m_scope_stack.last().scope_node == &scope_node); + m_scope_stack.take_last(); +} + +void Interpreter::do_return() +{ + dbg() << "FIXME: Implement Interpreter::do_return()"; +} + +} diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h new file mode 100644 index 0000000000..cdc4bda5a7 --- /dev/null +++ b/Libraries/LibJS/Interpreter.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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. + */ + +#pragma once + +#include +#include + +namespace JS { + +struct ScopeFrame { + const ScopeNode& scope_node; +}; + +class Interpreter { +public: + Interpreter(); + ~Interpreter(); + + Value run(const ScopeNode&); + + Object& global_object() { return *m_global_object; } + const Object& global_object() const { return *m_global_object; } + + void do_return(); + +private: + void enter_scope(const ScopeNode&); + void exit_scope(const ScopeNode&); + + Vector m_scope_stack; + + Object* m_global_object { nullptr }; +}; + +} diff --git a/Libraries/LibJS/Makefile b/Libraries/LibJS/Makefile new file mode 100644 index 0000000000..af5a558be4 --- /dev/null +++ b/Libraries/LibJS/Makefile @@ -0,0 +1,15 @@ +OBJS = \ + AST.o \ + Function.o \ + Interpreter.o \ + Object.o \ + Value.o + +LIBRARY = libjs.a + +install: + mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/LibJS/ + cp *.h $(SERENITY_BASE_DIR)/Root/usr/include/LibJS/ + cp $(LIBRARY) $(SERENITY_BASE_DIR)/Root/usr/lib/ + +include ../../Makefile.common diff --git a/Libraries/LibJS/Object.cpp b/Libraries/LibJS/Object.cpp new file mode 100644 index 0000000000..e7359807be --- /dev/null +++ b/Libraries/LibJS/Object.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include + +namespace JS { + +Value Object::get(String property_name) const +{ + return m_properties.get(property_name).value_or(js_undefined()); +} + +void Object::put(String property_name, Value value) +{ + m_properties.set(property_name, move(value)); +} + +} diff --git a/Libraries/LibJS/Object.h b/Libraries/LibJS/Object.h new file mode 100644 index 0000000000..7c1697b8c8 --- /dev/null +++ b/Libraries/LibJS/Object.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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. + */ + +#pragma once + +#include +#include + +namespace JS { + +class Object { +public: + Object() {} + virtual ~Object() {} + + Value get(String property_name) const; + void put(String property_name, Value); + + virtual bool is_function() const { return false; } + + virtual const char* class_name() const { return "Object"; } + +private: + HashMap m_properties; +}; + +} diff --git a/Libraries/LibJS/Value.cpp b/Libraries/LibJS/Value.cpp new file mode 100644 index 0000000000..50b4d0c3c0 --- /dev/null +++ b/Libraries/LibJS/Value.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include + +namespace JS { + +String Value::to_string() const +{ + if (is_boolean()) + return as_bool() ? "true" : "false"; + + if (is_null()) + return "null"; + + if (is_undefined()) + return "null"; + + if (is_number()) { + // FIXME: This needs improvement. + return String::number((i32)as_double()); + } + + if (is_object()) { + return String::format("{%s}", as_object()->class_name()); + } + + ASSERT_NOT_REACHED(); +} + +} diff --git a/Libraries/LibJS/Value.h b/Libraries/LibJS/Value.h new file mode 100644 index 0000000000..2513f5d37c --- /dev/null +++ b/Libraries/LibJS/Value.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS { + +class Value { +public: + enum class Type { + Undefined, + Null, + Number, + String, + Object, + Boolean, + }; + + bool is_undefined() const { return m_type == Type::Undefined; } + bool is_null() const { return m_type == Type::Null; } + bool is_number() const { return m_type == Type::Number; } + bool is_string() const { return m_type == Type::String; } + bool is_object() const { return m_type == Type::Object; } + bool is_boolean() const { return m_type == Type::Boolean; } + + explicit Value(double value) + : m_type(Type::Number) + { + m_value.as_double = value; + } + + explicit Value(i32 value) + : m_type(Type::Number) + { + m_value.as_double = value; + } + + explicit Value(Object* object) + : m_type(Type::Object) + { + m_value.as_object = object; + } + + explicit Value(Type type) + : m_type(type) + { + } + + Type type() const { return m_type; } + + double as_double() const + { + ASSERT(type() == Type::Number); + return m_value.as_double; + } + + bool as_bool() const + { + ASSERT(type() == Type::Boolean); + return m_value.as_bool; + } + + Object* as_object() + { + ASSERT(type() == Type::Object); + return m_value.as_object; + } + + const Object* as_object() const + { + ASSERT(type() == Type::Object); + return m_value.as_object; + } + + String to_string() const; + +private: + Type m_type { Type::Undefined }; + + union { + bool as_bool; + double as_double; + StringImpl* as_string; + Object* as_object; + } m_value; +}; + +inline Value js_undefined() +{ + return Value(Value::Type::Undefined); +} + +inline Value js_null() +{ + return Value(Value::Type::Null); +} + +inline const LogStream& operator<<(const LogStream& stream, const Value& value) +{ + switch (value.type()) { + case Value::Type::Boolean: + return stream << value.as_bool(); + case Value::Type::Number: + return stream << (i32)value.as_double(); + default: + ASSERT_NOT_REACHED(); + return stream; + } +} + +} diff --git a/Userland/Makefile b/Userland/Makefile index 8b60b9693a..f54982312e 100644 --- a/Userland/Makefile +++ b/Userland/Makefile @@ -4,7 +4,7 @@ APPS = ${SRCS:.cpp=} EXTRA_CLEAN = $(APPS) -LIB_DEPS = Web GUI Gfx Audio Protocol IPC Thread Pthread Core PCIDB Markdown +LIB_DEPS = Web GUI Gfx Audio Protocol IPC Thread Pthread Core PCIDB Markdown JS include ../Makefile.common diff --git a/Userland/js.cpp b/Userland/js.cpp new file mode 100644 index 0000000000..876d1ae0f5 --- /dev/null +++ b/Userland/js.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, Andreas Kling + * 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 +#include +#include +#include +#include +#include + +int main() +{ + auto program = make(); + + auto block = make(); + block->append( + make( + JS::BinaryOp::Plus, + make( + JS::BinaryOp::Plus, + make(JS::Value(1)), + make(JS::Value(2))), + make(JS::Value(3)))); + + program->append("foo", move(block)); + program->append("foo"); + + program->dump(0); + + JS::Interpreter interpreter; + auto result = interpreter.run(*program); + dbg() << "Interpreter returned " << result; + + printf("%s\n", result.to_string().characters()); + return 0; +}