2020-03-07 18:42:11 +00:00
/*
* 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 .
*/
2020-03-12 09:51:41 +00:00
# include <AK/ByteBuffer.h>
2020-03-07 18:42:11 +00:00
# include <AK/NonnullOwnPtr.h>
2020-03-25 22:10:29 +00:00
# include <AK/StringBuilder.h>
2020-03-12 09:51:41 +00:00
# include <LibCore/ArgsParser.h>
# include <LibCore/File.h>
2020-03-07 18:42:11 +00:00
# include <LibJS/AST.h>
# include <LibJS/Interpreter.h>
2020-03-11 18:27:43 +00:00
# include <LibJS/Parser.h>
2020-03-26 11:26:11 +00:00
# include <LibJS/Runtime/Array.h>
2020-03-31 17:43:45 +00:00
# include <LibJS/Runtime/Date.h>
2020-04-02 07:54:15 +00:00
# include <LibJS/Runtime/Error.h>
2020-03-26 13:54:43 +00:00
# include <LibJS/Runtime/Function.h>
2020-04-01 16:53:28 +00:00
# include <LibJS/Runtime/GlobalObject.h>
2020-03-16 13:20:30 +00:00
# include <LibJS/Runtime/Object.h>
# include <LibJS/Runtime/PrimitiveString.h>
2020-04-02 17:32:21 +00:00
# include <LibJS/Runtime/Shape.h>
2020-03-16 13:20:30 +00:00
# include <LibJS/Runtime/Value.h>
2020-03-31 11:34:57 +00:00
# include <LibLine/Editor.h>
2020-03-07 18:42:11 +00:00
# include <stdio.h>
2020-04-02 15:52:40 +00:00
Vector < String > repl_statements ;
2020-04-01 19:20:32 +00:00
class ReplObject : public JS : : GlobalObject {
public :
ReplObject ( ) ;
virtual ~ ReplObject ( ) override ;
private :
virtual const char * class_name ( ) const override { return " ReplObject " ; }
static JS : : Value exit_interpreter ( JS : : Interpreter & ) ;
static JS : : Value repl_help ( JS : : Interpreter & ) ;
static JS : : Value load_file ( JS : : Interpreter & ) ;
2020-04-02 15:52:40 +00:00
static JS : : Value save_to_file ( JS : : Interpreter & ) ;
2020-04-01 19:20:32 +00:00
} ;
2020-03-25 22:10:29 +00:00
bool dump_ast = false ;
2020-03-31 17:00:20 +00:00
static OwnPtr < Line : : Editor > editor ;
2020-03-25 22:10:29 +00:00
String read_next_piece ( )
{
StringBuilder piece ;
int level = 0 ;
2020-03-30 18:14:23 +00:00
StringBuilder prompt_builder ;
2020-03-25 22:10:29 +00:00
do {
2020-03-30 18:14:23 +00:00
prompt_builder . clear ( ) ;
prompt_builder . append ( " > " ) ;
for ( auto i = 0 ; i < level ; + + i )
prompt_builder . append ( " " ) ;
2020-03-31 17:00:20 +00:00
String line = editor - > get_line ( prompt_builder . build ( ) ) ;
2020-04-04 21:27:21 +00:00
editor - > add_to_history ( line ) ;
2020-03-25 22:10:29 +00:00
piece . append ( line ) ;
auto lexer = JS : : Lexer ( line ) ;
for ( JS : : Token token = lexer . next ( ) ; token . type ( ) ! = JS : : TokenType : : Eof ; token = lexer . next ( ) ) {
switch ( token . type ( ) ) {
case JS : : TokenType : : BracketOpen :
case JS : : TokenType : : CurlyOpen :
case JS : : TokenType : : ParenOpen :
level + + ;
break ;
case JS : : TokenType : : BracketClose :
case JS : : TokenType : : CurlyClose :
case JS : : TokenType : : ParenClose :
level - - ;
break ;
default :
break ;
}
}
} while ( level > 0 ) ;
return piece . to_string ( ) ;
}
2020-03-26 11:26:11 +00:00
static void print_value ( JS : : Value value , HashTable < JS : : Object * > & seen_objects ) ;
2020-04-01 20:18:47 +00:00
static void print_array ( const JS : : Array & array , HashTable < JS : : Object * > & seen_objects )
2020-03-26 11:26:11 +00:00
{
fputs ( " [ " , stdout ) ;
2020-04-01 20:18:47 +00:00
for ( size_t i = 0 ; i < array . elements ( ) . size ( ) ; + + i ) {
print_value ( array . elements ( ) [ i ] , seen_objects ) ;
if ( i ! = array . elements ( ) . size ( ) - 1 )
2020-03-26 11:26:11 +00:00
fputs ( " , " , stdout ) ;
}
fputs ( " ] " , stdout ) ;
}
2020-04-01 20:18:47 +00:00
static void print_object ( const JS : : Object & object , HashTable < JS : : Object * > & seen_objects )
2020-03-26 11:26:11 +00:00
{
fputs ( " { " , stdout ) ;
size_t index = 0 ;
2020-04-02 17:32:21 +00:00
for ( auto & it : object . shape ( ) . property_table ( ) ) {
2020-03-26 11:26:11 +00:00
printf ( " \" \033 [33;1m%s \033 [0m \" : " , it . key . characters ( ) ) ;
2020-04-02 17:32:21 +00:00
print_value ( object . get_direct ( it . value . offset ) , seen_objects ) ;
if ( index ! = object . shape ( ) . property_table ( ) . size ( ) - 1 )
2020-03-26 11:26:11 +00:00
fputs ( " , " , stdout ) ;
+ + index ;
}
fputs ( " } " , stdout ) ;
}
2020-04-01 20:18:47 +00:00
static void print_function ( const JS : : Object & function , HashTable < JS : : Object * > & )
2020-03-26 13:54:43 +00:00
{
2020-04-01 20:18:47 +00:00
printf ( " \033 [34;1m[%s] \033 [0m " , function . class_name ( ) ) ;
2020-03-26 13:54:43 +00:00
}
2020-04-01 20:18:47 +00:00
static void print_date ( const JS : : Object & date , HashTable < JS : : Object * > & )
2020-03-31 17:43:45 +00:00
{
2020-04-01 20:18:47 +00:00
printf ( " \033 [34;1mDate %s \033 [0m " , static_cast < const JS : : Date & > ( date ) . string ( ) . characters ( ) ) ;
2020-03-31 17:43:45 +00:00
}
2020-04-02 07:54:15 +00:00
static void print_error ( const JS : : Object & object , HashTable < JS : : Object * > & )
{
auto & error = static_cast < const JS : : Error & > ( object ) ;
2020-04-02 13:18:19 +00:00
printf ( " \033 [34;1m[%s] \033 [0m " , error . name ( ) . characters ( ) ) ;
if ( ! error . message ( ) . is_empty ( ) )
printf ( " : %s " , error . message ( ) . characters ( ) ) ;
2020-04-02 07:54:15 +00:00
}
2020-03-26 11:26:11 +00:00
void print_value ( JS : : Value value , HashTable < JS : : Object * > & seen_objects )
{
if ( value . is_object ( ) ) {
2020-04-01 20:18:47 +00:00
if ( seen_objects . contains ( & value . as_object ( ) ) ) {
2020-03-26 11:26:11 +00:00
// FIXME: Maybe we should only do this for circular references,
// not for all reoccurring objects.
2020-04-01 20:18:47 +00:00
printf ( " <already printed Object %p> " , & value . as_object ( ) ) ;
2020-03-26 11:26:11 +00:00
return ;
}
2020-04-01 20:18:47 +00:00
seen_objects . set ( & value . as_object ( ) ) ;
2020-03-26 11:26:11 +00:00
}
if ( value . is_array ( ) )
2020-04-01 20:18:47 +00:00
return print_array ( static_cast < const JS : : Array & > ( value . as_object ( ) ) , seen_objects ) ;
2020-04-01 16:53:28 +00:00
2020-03-31 17:43:45 +00:00
if ( value . is_object ( ) ) {
2020-04-01 20:18:47 +00:00
auto & object = value . as_object ( ) ;
if ( object . is_function ( ) )
2020-03-31 17:43:45 +00:00
return print_function ( object , seen_objects ) ;
2020-04-01 20:18:47 +00:00
if ( object . is_date ( ) )
2020-03-31 17:43:45 +00:00
return print_date ( object , seen_objects ) ;
2020-04-02 07:54:15 +00:00
if ( object . is_error ( ) )
return print_error ( object , seen_objects ) ;
2020-03-31 17:43:45 +00:00
return print_object ( object , seen_objects ) ;
}
2020-03-26 11:26:11 +00:00
if ( value . is_string ( ) )
printf ( " \033 [31;1m " ) ;
else if ( value . is_number ( ) )
printf ( " \033 [35;1m " ) ;
else if ( value . is_boolean ( ) )
printf ( " \033 [32;1m " ) ;
2020-03-26 13:54:43 +00:00
else if ( value . is_null ( ) )
printf ( " \033 [33;1m " ) ;
else if ( value . is_undefined ( ) )
2020-03-26 11:26:11 +00:00
printf ( " \033 [34;1m " ) ;
if ( value . is_string ( ) )
putchar ( ' " ' ) ;
printf ( " %s " , value . to_string ( ) . characters ( ) ) ;
if ( value . is_string ( ) )
putchar ( ' " ' ) ;
printf ( " \033 [0m " ) ;
}
static void print ( JS : : Value value )
{
HashTable < JS : : Object * > seen_objects ;
print_value ( value , seen_objects ) ;
putchar ( ' \n ' ) ;
}
2020-04-01 19:20:32 +00:00
bool file_has_shebang ( AK : : ByteBuffer file_contents )
{
if ( file_contents . size ( ) > = 2 & & file_contents [ 0 ] = = ' # ' & & file_contents [ 1 ] = = ' ! ' )
return true ;
return false ;
}
StringView strip_shebang ( AK : : ByteBuffer file_contents )
{
size_t i = 0 ;
for ( i = 2 ; i < file_contents . size ( ) ; + + i ) {
if ( file_contents [ i ] = = ' \n ' )
break ;
}
return StringView ( ( const char * ) file_contents . data ( ) + i , file_contents . size ( ) - i ) ;
}
2020-04-02 15:52:40 +00:00
bool write_to_file ( const StringView & path )
{
int fd = open_with_path_length ( path . characters_without_null_termination ( ) , path . length ( ) , O_WRONLY | O_CREAT | O_TRUNC , 0666 ) ;
for ( size_t i = 0 ; i < repl_statements . size ( ) ; i + + ) {
auto line = repl_statements [ i ] ;
if ( line . length ( ) & & i ! = repl_statements . size ( ) - 1 ) {
ssize_t nwritten = write ( fd , line . characters ( ) , line . length ( ) ) ;
if ( nwritten < 0 ) {
close ( fd ) ;
return false ;
}
}
if ( i ! = repl_statements . size ( ) - 1 ) {
char ch = ' \n ' ;
ssize_t nwritten = write ( fd , & ch , 1 ) ;
if ( nwritten ! = 1 ) {
perror ( " write " ) ;
close ( fd ) ;
return false ;
}
}
}
close ( fd ) ;
return true ;
}
2020-04-01 19:20:32 +00:00
ReplObject : : ReplObject ( )
{
put_native_function ( " exit " , exit_interpreter ) ;
put_native_function ( " help " , repl_help ) ;
2020-04-04 13:13:53 +00:00
put_native_function ( " load " , load_file , 1 ) ;
put_native_function ( " save " , save_to_file , 1 ) ;
2020-04-01 19:20:32 +00:00
}
ReplObject : : ~ ReplObject ( )
{
}
2020-04-02 15:52:40 +00:00
JS : : Value ReplObject : : save_to_file ( JS : : Interpreter & interpreter )
{
if ( ! interpreter . argument_count ( ) )
return JS : : Value ( false ) ;
String save_path = interpreter . argument ( 0 ) . to_string ( ) ;
StringView path = StringView ( save_path . characters ( ) ) ;
if ( write_to_file ( path ) ) {
return JS : : Value ( true ) ;
}
return JS : : Value ( false ) ;
}
2020-04-01 19:20:32 +00:00
JS : : Value ReplObject : : exit_interpreter ( JS : : Interpreter & interpreter )
{
2020-04-01 20:38:59 +00:00
if ( ! interpreter . argument_count ( ) )
2020-04-01 19:20:32 +00:00
exit ( 0 ) ;
2020-04-01 20:38:59 +00:00
int exit_code = interpreter . argument ( 0 ) . to_number ( ) . as_double ( ) ;
2020-04-01 19:20:32 +00:00
exit ( exit_code ) ;
return JS : : js_undefined ( ) ;
}
JS : : Value ReplObject : : repl_help ( JS : : Interpreter & interpreter )
{
StringBuilder help_text ;
help_text . append ( " REPL commands: \n " ) ;
help_text . append ( " exit(code): exit the REPL with specified code. Defaults to 0. \n " ) ;
help_text . append ( " help(): display this menu \n " ) ;
help_text . append ( " load(files): Accepts file names as params to load into running session. For example repl.load( \" js/1.js \" , \" js/2.js \" , \" js/3.js \" ) \n " ) ;
String result = help_text . to_string ( ) ;
2020-04-04 10:57:37 +00:00
return js_string ( interpreter , result ) ;
2020-04-01 19:20:32 +00:00
}
JS : : Value ReplObject : : load_file ( JS : : Interpreter & interpreter )
{
2020-04-01 20:38:59 +00:00
if ( ! interpreter . argument_count ( ) )
2020-04-01 19:20:32 +00:00
return JS : : Value ( false ) ;
2020-04-01 20:38:59 +00:00
for ( auto & file : interpreter . call_frame ( ) . arguments ) {
2020-04-01 19:20:32 +00:00
String file_name = file . as_string ( ) - > string ( ) ;
auto js_file = Core : : File : : construct ( file_name ) ;
if ( ! js_file - > open ( Core : : IODevice : : ReadOnly ) ) {
fprintf ( stderr , " Failed to open %s: %s \n " , file_name . characters ( ) , js_file - > error_string ( ) ) ;
}
auto file_contents = js_file - > read_all ( ) ;
StringView source ;
if ( file_has_shebang ( file_contents ) ) {
source = strip_shebang ( file_contents ) ;
} else {
source = file_contents ;
}
auto program = JS : : Parser ( JS : : Lexer ( source ) ) . parse_program ( ) ;
if ( dump_ast )
program - > dump ( 0 ) ;
2020-04-04 21:45:13 +00:00
interpreter . run ( * program ) ;
print ( interpreter . last_value ( ) ) ;
2020-04-01 19:20:32 +00:00
}
return JS : : Value ( true ) ;
}
2020-03-25 22:10:29 +00:00
void repl ( JS : : Interpreter & interpreter )
{
while ( true ) {
String piece = read_next_piece ( ) ;
if ( piece . is_empty ( ) )
2020-03-31 11:29:42 +00:00
continue ;
2020-04-02 15:52:40 +00:00
repl_statements . append ( piece ) ;
2020-03-25 22:10:29 +00:00
auto program = JS : : Parser ( JS : : Lexer ( piece ) ) . parse_program ( ) ;
if ( dump_ast )
program - > dump ( 0 ) ;
2020-04-04 21:45:13 +00:00
interpreter . run ( * program ) ;
2020-04-02 07:54:15 +00:00
if ( interpreter . exception ( ) ) {
2020-04-02 13:25:08 +00:00
printf ( " Uncaught exception: " ) ;
2020-04-02 07:54:15 +00:00
print ( interpreter . exception ( ) - > value ( ) ) ;
interpreter . clear_exception ( ) ;
} else {
2020-04-04 21:45:13 +00:00
print ( interpreter . last_value ( ) ) ;
2020-04-02 07:54:15 +00:00
}
2020-03-25 22:10:29 +00:00
}
}
2020-03-09 20:58:27 +00:00
2020-04-05 12:49:46 +00:00
JS : : Value assert_impl ( JS : : Interpreter & interpreter )
{
if ( ! interpreter . argument_count ( ) )
return interpreter . throw_exception < JS : : Error > ( " TypeError " , " No arguments specified " ) ;
auto assertion_value = interpreter . argument ( 0 ) ;
if ( ! assertion_value . is_boolean ( ) )
return interpreter . throw_exception < JS : : Error > ( " TypeError " , " The first argument is not a boolean " ) ;
if ( ! assertion_value . to_boolean ( ) )
return interpreter . throw_exception < JS : : Error > ( " AssertionError " , " The assertion failed! " ) ;
return assertion_value ;
}
2020-03-12 09:51:41 +00:00
int main ( int argc , char * * argv )
2020-03-07 18:42:11 +00:00
{
2020-03-16 18:18:46 +00:00
bool gc_on_every_allocation = false ;
2020-03-20 13:52:27 +00:00
bool print_last_result = false ;
2020-04-05 12:49:46 +00:00
bool test_mode = false ;
2020-03-12 09:51:41 +00:00
const char * script_path = nullptr ;
Core : : ArgsParser args_parser ;
2020-03-26 07:03:17 +00:00
args_parser . add_option ( dump_ast , " Dump the AST " , " dump-ast " , ' A ' ) ;
2020-03-24 13:45:41 +00:00
args_parser . add_option ( print_last_result , " Print last result " , " print-last-result " , ' l ' ) ;
2020-03-16 18:18:46 +00:00
args_parser . add_option ( gc_on_every_allocation , " GC on every allocation " , " gc-on-every-allocation " , ' g ' ) ;
2020-04-05 12:49:46 +00:00
args_parser . add_option ( test_mode , " Run the interpretter with added functionality for the test harness " , " test-mode " , ' t ' ) ;
2020-03-25 22:10:29 +00:00
args_parser . add_positional_argument ( script_path , " Path to script file " , " script " , Core : : ArgsParser : : Required : : No ) ;
2020-03-12 09:51:41 +00:00
args_parser . parse ( argc , argv ) ;
2020-03-25 22:10:29 +00:00
if ( script_path = = nullptr ) {
2020-04-01 19:20:32 +00:00
auto interpreter = JS : : Interpreter : : create < ReplObject > ( ) ;
interpreter - > heap ( ) . set_should_collect_on_every_allocation ( gc_on_every_allocation ) ;
interpreter - > global_object ( ) . put ( " global " , & interpreter - > global_object ( ) ) ;
2020-04-05 12:49:46 +00:00
if ( test_mode ) {
interpreter - > global_object ( ) . put_native_function ( " assert " , assert_impl ) ;
}
2020-04-01 19:20:32 +00:00
2020-03-31 17:00:20 +00:00
editor = make < Line : : Editor > ( ) ;
editor - > initialize ( ) ;
2020-04-01 19:04:51 +00:00
repl ( * interpreter ) ;
2020-03-25 22:10:29 +00:00
} else {
2020-04-01 19:20:32 +00:00
auto interpreter = JS : : Interpreter : : create < JS : : GlobalObject > ( ) ;
interpreter - > heap ( ) . set_should_collect_on_every_allocation ( gc_on_every_allocation ) ;
interpreter - > global_object ( ) . put ( " global " , & interpreter - > global_object ( ) ) ;
2020-04-05 12:49:46 +00:00
if ( test_mode ) {
interpreter - > global_object ( ) . put_native_function ( " assert " , assert_impl ) ;
}
2020-04-01 19:20:32 +00:00
2020-03-25 22:10:29 +00:00
auto file = Core : : File : : construct ( script_path ) ;
if ( ! file - > open ( Core : : IODevice : : ReadOnly ) ) {
fprintf ( stderr , " Failed to open %s: %s \n " , script_path , file - > error_string ( ) ) ;
return 1 ;
}
auto file_contents = file - > read_all ( ) ;
StringView source ;
2020-04-01 19:20:32 +00:00
if ( file_has_shebang ( file_contents ) ) {
source = strip_shebang ( file_contents ) ;
2020-03-25 22:10:29 +00:00
} else {
source = file_contents ;
}
auto program = JS : : Parser ( JS : : Lexer ( source ) ) . parse_program ( ) ;
2020-03-12 09:51:41 +00:00
2020-03-25 22:10:29 +00:00
if ( dump_ast )
program - > dump ( 0 ) ;
2020-03-07 18:42:11 +00:00
2020-04-01 19:04:51 +00:00
auto result = interpreter - > run ( * program ) ;
2020-03-07 18:42:11 +00:00
2020-04-02 14:55:37 +00:00
if ( interpreter - > exception ( ) ) {
printf ( " Uncaught exception: " ) ;
print ( interpreter - > exception ( ) - > value ( ) ) ;
interpreter - > clear_exception ( ) ;
2020-04-03 20:52:49 +00:00
return 1 ;
2020-04-02 14:55:37 +00:00
}
2020-04-03 20:52:49 +00:00
if ( print_last_result )
print ( result ) ;
2020-03-25 22:10:29 +00:00
}
2020-03-08 18:59:59 +00:00
2020-03-07 18:42:11 +00:00
return 0 ;
}