mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 00:32:45 +00:00 
			
		
		
		
	LibJS: Add NativeFunction, a callable wrapper around a C++ lambda
This can be used to implement arbitrary functionality, callable from
JavaScript.
To make this work, I had to change the way CallExpression passes
arguments to the callee. Instead of a HashMap<String, Value>, we now
pass an ordered list of Argument { String name; Value value; }.
This patch includes a native "print(argument)" function. :^)
			
			
This commit is contained in:
		
							parent
							
								
									cc8e3048bc
								
							
						
					
					
						commit
						7912f33ea0
					
				
					 9 changed files with 129 additions and 17 deletions
				
			
		
							
								
								
									
										1
									
								
								Base/home/anon/js/native-function.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Base/home/anon/js/native-function.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | print("Hello friends!") | ||||||
|  | @ -29,6 +29,7 @@ | ||||||
| #include <LibJS/AST.h> | #include <LibJS/AST.h> | ||||||
| #include <LibJS/Function.h> | #include <LibJS/Function.h> | ||||||
| #include <LibJS/Interpreter.h> | #include <LibJS/Interpreter.h> | ||||||
|  | #include <LibJS/NativeFunction.h> | ||||||
| #include <LibJS/PrimitiveString.h> | #include <LibJS/PrimitiveString.h> | ||||||
| #include <LibJS/Value.h> | #include <LibJS/Value.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  | @ -62,20 +63,25 @@ Value CallExpression::execute(Interpreter& interpreter) const | ||||||
|     auto callee = interpreter.get_variable(name()); |     auto callee = interpreter.get_variable(name()); | ||||||
|     ASSERT(callee.is_object()); |     ASSERT(callee.is_object()); | ||||||
|     auto* callee_object = callee.as_object(); |     auto* callee_object = callee.as_object(); | ||||||
|     ASSERT(callee_object->is_function()); |  | ||||||
|     auto& function = static_cast<Function&>(*callee_object); |  | ||||||
| 
 | 
 | ||||||
|     const size_t arguments_size = m_arguments.size(); |     Vector<Argument> passed_arguments; | ||||||
|     ASSERT(function.parameters().size() == arguments_size); |     for (size_t i = 0; i < m_arguments.size(); ++i) { | ||||||
|     HashMap<String, Value> passed_parameters; |         String name; | ||||||
|     for (size_t i = 0; i < arguments_size; ++i) { |         if (callee_object->is_function()) | ||||||
|         auto name = function.parameters()[i]; |             name = static_cast<Function&>(*callee_object).parameters()[i]; | ||||||
|         auto value = m_arguments[i].execute(interpreter); |         auto value = m_arguments[i].execute(interpreter); | ||||||
|         dbg() << name << ": " << value; |         dbg() << name << ": " << value; | ||||||
|         passed_parameters.set(move(name), move(value)); |         passed_arguments.append({ move(name), move(value) }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return interpreter.run(function.body(), move(passed_parameters), ScopeType::Function); |     if (callee_object->is_function()) | ||||||
|  |         return interpreter.run(static_cast<Function&>(*callee_object).body(), move(passed_arguments), ScopeType::Function); | ||||||
|  | 
 | ||||||
|  |     if (callee_object->is_native_function()) { | ||||||
|  |         return static_cast<NativeFunction&>(*callee_object).native_function()(move(passed_arguments)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ASSERT_NOT_REACHED(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Value ReturnStatement::execute(Interpreter& interpreter) const | Value ReturnStatement::execute(Interpreter& interpreter) const | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ | ||||||
| namespace JS { | namespace JS { | ||||||
| 
 | 
 | ||||||
| class ASTNode; | class ASTNode; | ||||||
|  | class Argument; | ||||||
| class Cell; | class Cell; | ||||||
| class Expression; | class Expression; | ||||||
| class Heap; | class Heap; | ||||||
|  |  | ||||||
|  | @ -27,8 +27,10 @@ | ||||||
| #include <AK/Badge.h> | #include <AK/Badge.h> | ||||||
| #include <LibJS/AST.h> | #include <LibJS/AST.h> | ||||||
| #include <LibJS/Interpreter.h> | #include <LibJS/Interpreter.h> | ||||||
|  | #include <LibJS/NativeFunction.h> | ||||||
| #include <LibJS/Object.h> | #include <LibJS/Object.h> | ||||||
| #include <LibJS/Value.h> | #include <LibJS/Value.h> | ||||||
|  | #include <stdio.h> | ||||||
| 
 | 
 | ||||||
| namespace JS { | namespace JS { | ||||||
| 
 | 
 | ||||||
|  | @ -36,15 +38,20 @@ Interpreter::Interpreter() | ||||||
|     : m_heap(*this) |     : m_heap(*this) | ||||||
| { | { | ||||||
|     m_global_object = heap().allocate<Object>(); |     m_global_object = heap().allocate<Object>(); | ||||||
|  |     m_global_object->put("print", Value(heap().allocate<NativeFunction>([](Vector<Argument> arguments) -> Value { | ||||||
|  |         for (auto& argument : arguments) | ||||||
|  |             printf("%s ", argument.value.to_string().characters()); | ||||||
|  |         return js_undefined(); | ||||||
|  |     }))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Interpreter::~Interpreter() | Interpreter::~Interpreter() | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Value Interpreter::run(const ScopeNode& scope_node, HashMap<String, Value> scope_variables, ScopeType scope_type) | Value Interpreter::run(const ScopeNode& scope_node, Vector<Argument> arguments, ScopeType scope_type) | ||||||
| { | { | ||||||
|     enter_scope(scope_node, move(scope_variables), scope_type); |     enter_scope(scope_node, move(arguments), scope_type); | ||||||
| 
 | 
 | ||||||
|     Value last_value = js_undefined(); |     Value last_value = js_undefined(); | ||||||
|     for (auto& node : scope_node.children()) { |     for (auto& node : scope_node.children()) { | ||||||
|  | @ -55,13 +62,12 @@ Value Interpreter::run(const ScopeNode& scope_node, HashMap<String, Value> scope | ||||||
|     return last_value; |     return last_value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Interpreter::enter_scope(const ScopeNode& scope_node, HashMap<String, Value> scope_variables, ScopeType scope_type) | void Interpreter::enter_scope(const ScopeNode& scope_node, Vector<Argument> arguments, ScopeType scope_type) | ||||||
| { | { | ||||||
|     HashMap<String, Variable> scope_variables_with_declaration_type; |     HashMap<String, Variable> scope_variables_with_declaration_type; | ||||||
|     for (String name : scope_variables.keys()) { |     for (auto& argument : arguments) { | ||||||
|         scope_variables_with_declaration_type.set(name, { scope_variables.get(name).value(), DeclarationType::Var }); |         scope_variables_with_declaration_type.set(argument.name, { argument.value, DeclarationType::Var }); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     m_scope_stack.append({ scope_type, scope_node, move(scope_variables_with_declaration_type) }); |     m_scope_stack.append({ scope_type, scope_node, move(scope_variables_with_declaration_type) }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <AK/HashMap.h> | #include <AK/HashMap.h> | ||||||
|  | #include <AK/String.h> | ||||||
| #include <AK/Vector.h> | #include <AK/Vector.h> | ||||||
| #include <LibJS/Forward.h> | #include <LibJS/Forward.h> | ||||||
| #include <LibJS/Heap.h> | #include <LibJS/Heap.h> | ||||||
|  | @ -50,12 +51,17 @@ struct ScopeFrame { | ||||||
|     HashMap<String, Variable> variables; |     HashMap<String, Variable> variables; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct Argument { | ||||||
|  |     String name; | ||||||
|  |     Value value; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| class Interpreter { | class Interpreter { | ||||||
| public: | public: | ||||||
|     Interpreter(); |     Interpreter(); | ||||||
|     ~Interpreter(); |     ~Interpreter(); | ||||||
| 
 | 
 | ||||||
|     Value run(const ScopeNode&, HashMap<String, Value> scope_variables = {}, ScopeType = ScopeType::Block); |     Value run(const ScopeNode&, Vector<Argument> = {}, ScopeType = ScopeType::Block); | ||||||
| 
 | 
 | ||||||
|     Object& global_object() { return *m_global_object; } |     Object& global_object() { return *m_global_object; } | ||||||
|     const Object& global_object() const { return *m_global_object; } |     const Object& global_object() const { return *m_global_object; } | ||||||
|  | @ -71,7 +77,7 @@ public: | ||||||
|     void collect_roots(Badge<Heap>, HashTable<Cell*>&); |     void collect_roots(Badge<Heap>, HashTable<Cell*>&); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     void enter_scope(const ScopeNode&, HashMap<String, Value> scope_variables, ScopeType); |     void enter_scope(const ScopeNode&, Vector<Argument>, ScopeType); | ||||||
|     void exit_scope(const ScopeNode&); |     void exit_scope(const ScopeNode&); | ||||||
| 
 | 
 | ||||||
|     Heap m_heap; |     Heap m_heap; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ OBJS = \ | ||||||
|     HeapBlock.o \
 |     HeapBlock.o \
 | ||||||
|     Interpreter.o \
 |     Interpreter.o \
 | ||||||
|     Lexer.o \
 |     Lexer.o \
 | ||||||
|  |     NativeFunction.o \
 | ||||||
|     Object.o \
 |     Object.o \
 | ||||||
|     Parser.o \
 |     Parser.o \
 | ||||||
|     PrimitiveString.o \
 |     PrimitiveString.o \
 | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								Libraries/LibJS/NativeFunction.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Libraries/LibJS/NativeFunction.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | /*
 | ||||||
|  |  * 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/Interpreter.h> | ||||||
|  | #include <LibJS/NativeFunction.h> | ||||||
|  | #include <LibJS/Value.h> | ||||||
|  | 
 | ||||||
|  | namespace JS { | ||||||
|  | 
 | ||||||
|  | NativeFunction::NativeFunction(AK::Function<Value(Vector<Argument>)> native_function) | ||||||
|  |     : m_native_function(move(native_function)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | NativeFunction::~NativeFunction() | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								Libraries/LibJS/NativeFunction.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								Libraries/LibJS/NativeFunction.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | /*
 | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <AK/Function.h> | ||||||
|  | #include <LibJS/Object.h> | ||||||
|  | 
 | ||||||
|  | namespace JS { | ||||||
|  | 
 | ||||||
|  | class NativeFunction final : public Object { | ||||||
|  | public: | ||||||
|  |     explicit NativeFunction(AK::Function<Value(Vector<Argument>)>); | ||||||
|  |     virtual ~NativeFunction() override; | ||||||
|  | 
 | ||||||
|  |     AK::Function<Value(Vector<Argument>)>& native_function() { return m_native_function; } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     virtual bool is_native_function() const override { return true; } | ||||||
|  |     virtual const char* class_name() const override { return "NativeFunction"; } | ||||||
|  | 
 | ||||||
|  |     AK::Function<Value(Vector<Argument>)> m_native_function; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -42,6 +42,7 @@ public: | ||||||
|     void put(String property_name, Value); |     void put(String property_name, Value); | ||||||
| 
 | 
 | ||||||
|     virtual bool is_function() const { return false; } |     virtual bool is_function() const { return false; } | ||||||
|  |     virtual bool is_native_function() const { return false; } | ||||||
| 
 | 
 | ||||||
|     virtual const char* class_name() const override { return "Object"; } |     virtual const char* class_name() const override { return "Object"; } | ||||||
|     virtual void visit_children(Cell::Visitor&) override; |     virtual void visit_children(Cell::Visitor&) override; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling