Huge progress. Now actually working.
This commit is contained in:
		
							parent
							
								
									9661ad339a
								
							
						
					
					
						commit
						0826dcfdda
					
				|  | @ -418,14 +418,6 @@ struct cons_pointer make_cons( struct cons_pointer car, | |||
|  */ | ||||
| struct cons_pointer make_function( struct cons_pointer src, | ||||
|                                    struct cons_pointer ( *executable ) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|                                     ( struct stack_frame *, | ||||
|                                       struct cons_pointer ) ); | ||||
| 
 | ||||
|  | @ -434,14 +426,6 @@ struct cons_pointer make_function( struct cons_pointer src, | |||
|  */ | ||||
| struct cons_pointer make_special( struct cons_pointer src, | ||||
|                                   struct cons_pointer ( *executable ) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|                                    ( struct cons_pointer s_expr, | ||||
|                                      struct cons_pointer env, | ||||
|                                      struct stack_frame * frame ) ); | ||||
|  |  | |||
							
								
								
									
										35
									
								
								src/init.c
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/init.c
									
									
									
									
									
								
							|  | @ -19,19 +19,21 @@ | |||
| #include "consspaceobject.h" | ||||
| #include "intern.h" | ||||
| #include "lispops.h" | ||||
| #include "peano.h" | ||||
| #include "print.h" | ||||
| #include "repl.h" | ||||
| 
 | ||||
| void bind_function( char *name, struct cons_pointer ( *executable ) | ||||
|                      ( struct stack_frame *, struct cons_pointer ) ) { | ||||
|     deep_bind( intern( c_string_to_lisp_symbol( name ), oblist ), | ||||
|                make_function( NIL, executable ) ); | ||||
|     deep_bind( c_string_to_lisp_symbol( name ),  | ||||
|                make_function( NIL, executable )); | ||||
| } | ||||
| 
 | ||||
| void bind_special( char *name, struct cons_pointer ( *executable ) | ||||
|                     ( struct cons_pointer s_expr, struct cons_pointer env, | ||||
|                       struct stack_frame * frame ) ) { | ||||
|     deep_bind( intern( c_string_to_lisp_symbol( name ), oblist ), | ||||
|                make_special( NIL, executable ) ); | ||||
|     deep_bind( c_string_to_lisp_symbol( name ), | ||||
|                make_special( NIL, executable )); | ||||
| } | ||||
| 
 | ||||
| int main( int argc, char *argv[] ) { | ||||
|  | @ -70,17 +72,13 @@ int main( int argc, char *argv[] ) { | |||
|     /*
 | ||||
|      * privileged variables (keywords)  | ||||
|      */ | ||||
|     /*
 | ||||
|     deep_bind( intern( c_string_to_lisp_string( "nil" ), oblist ), NIL ); | ||||
|     deep_bind( intern( c_string_to_lisp_string( "t" ), oblist ), TRUE ); | ||||
|      */ | ||||
|     struct cons_pointer lisp_symbol = c_string_to_lisp_symbol( "oblist"); | ||||
|     deep_bind( lisp_symbol, &oblist); | ||||
|      | ||||
|     deep_bind( c_string_to_lisp_symbol( "nil" ), NIL ); | ||||
|     deep_bind( c_string_to_lisp_symbol( "t" ), TRUE ); | ||||
| 
 | ||||
|     /*
 | ||||
|      * primitive function operations  | ||||
|      */ | ||||
|     /*
 | ||||
|     bind_function( "assoc", &lisp_assoc ); | ||||
|     bind_function( "car", &lisp_car ); | ||||
|     bind_function( "cdr", &lisp_cdr ); | ||||
|  | @ -89,19 +87,20 @@ int main( int argc, char *argv[] ) { | |||
|     bind_function( "equal", &lisp_equal ); | ||||
|     bind_function( "read", &lisp_read ); | ||||
|     bind_function( "print", &lisp_print ); | ||||
| */ | ||||
|      | ||||
|     bind_function( "plus", &lisp_plus); | ||||
| 
 | ||||
|     /*
 | ||||
|      * primitive special forms  | ||||
|      */ | ||||
|     /*
 | ||||
|     bind_special( "apply", &lisp_apply ); | ||||
|     bind_special( "eval", &lisp_eval ); | ||||
|     bind_special( "quote", &lisp_quote ); | ||||
|     */ | ||||
|     if ( show_prompt) { | ||||
|         fwprintf( stderr, L"Oblist: "); | ||||
|         print(stderr, *oblist); | ||||
|     } | ||||
|      | ||||
|      | ||||
|     /* bind the oblist last, at this stage. Something clever needs to be done 
 | ||||
|      * here and I'm not sure what it is. */ | ||||
|     deep_bind( c_string_to_lisp_symbol( "oblist"), oblist); | ||||
|      | ||||
|     repl( stdin, stdout, stderr, show_prompt ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
|  * as a cons-space object. Cell may in principle be any kind of number, | ||||
|  * but only integers and reals are so far implemented. | ||||
|  */ | ||||
| double numeric_value( struct cons_pointer pointer ) { | ||||
| long double numeric_value( struct cons_pointer pointer ) { | ||||
|     double result = NAN; | ||||
|     struct cons_space_object *cell = &pointer2cell( pointer ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ | |||
| #ifndef __integer_h | ||||
| #define __integer_h | ||||
| 
 | ||||
| double numeric_value( struct cons_pointer pointer ); | ||||
| long double numeric_value( struct cons_pointer pointer ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate an integer cell representing this value and return a cons pointer to it. | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ | |||
|  * they're visible to all users/threads, but again I don't yet have any idea how  | ||||
|  * that will work. | ||||
|  */ | ||||
| struct cons_pointer oblist = & NIL; | ||||
| struct cons_pointer oblist = NIL; | ||||
| 
 | ||||
| /**
 | ||||
|  * Implementation of interned? in C. The final implementation if interned? will | ||||
|  | @ -104,7 +104,7 @@ bind( struct cons_pointer key, struct cons_pointer value, | |||
|  */ | ||||
| struct cons_pointer | ||||
| deep_bind( struct cons_pointer key, struct cons_pointer value ) { | ||||
|     oblist = &bind( key, value, *oblist ); | ||||
|     oblist = bind( key, value, oblist ); | ||||
|     return oblist; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ | |||
| #ifndef __intern_h | ||||
| #define __intern_h | ||||
| 
 | ||||
| extern struct cons_pointer * oblist; | ||||
| extern struct cons_pointer oblist; | ||||
| 
 | ||||
| /**
 | ||||
|  * return the value associated with this key in this store. In the current  | ||||
|  |  | |||
|  | @ -102,9 +102,10 @@ eval_cons( struct cons_pointer s_expr, struct cons_pointer env, | |||
|     switch ( fn_cell.tag.value ) { | ||||
|     case SPECIALTV: | ||||
|         { | ||||
|             struct cons_space_object special = pointer2cell( fn_pointer ); | ||||
|             struct stack_frame *frame = | ||||
|                 make_special_frame( my_frame, args, env ); | ||||
|             result = | ||||
|                 ( *special.payload.special.executable ) ( args, env, my_frame ); | ||||
|                 ( *fn_cell.payload.special.executable ) ( args, env, frame ); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|  | @ -121,7 +122,7 @@ eval_cons( struct cons_pointer s_expr, struct cons_pointer env, | |||
|              * the trick: pass the remaining arguments and environment to the | ||||
|              * executable code which is the payload of the function object.  | ||||
|              */ | ||||
|             result = ( *function.payload.function.executable ) ( frame, env ); | ||||
|             result = ( *fn_cell.payload.function.executable ) ( frame, env ); | ||||
|             free_stack_frame( frame ); | ||||
|         } | ||||
|         break; | ||||
|  | @ -165,13 +166,7 @@ lisp_eval( struct cons_pointer s_expr, struct cons_pointer env, | |||
| 
 | ||||
|     switch ( cell.tag.value ) { | ||||
|     case CONSTV: | ||||
|         fwprintf( stderr, L"In eval; about to make stack frame" ); | ||||
|         struct stack_frame *frame = make_stack_frame( previous, s_expr, env ); | ||||
|         fwprintf( stderr, L"In eval; stack frame made" ); | ||||
| 
 | ||||
|         result = eval_cons( s_expr, env, frame ); | ||||
| 
 | ||||
|         free_stack_frame( frame ); | ||||
|         result = eval_cons( s_expr, env, previous); | ||||
|         break; | ||||
| 
 | ||||
|     case SYMBOLTV: | ||||
|  | @ -181,7 +176,7 @@ lisp_eval( struct cons_pointer s_expr, struct cons_pointer env, | |||
|                 struct cons_pointer message = | ||||
|                     c_string_to_lisp_string | ||||
|                     ( "Attempt to take value of unbound symbol." ); | ||||
|                 result = lisp_throw( message, frame ); | ||||
|                 result = lisp_throw( message, previous ); | ||||
|             } else { | ||||
|                 result = c_assoc( canonical, env ); | ||||
|             } | ||||
|  |  | |||
							
								
								
									
										50
									
								
								src/peano.c
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/peano.c
									
									
									
									
									
								
							|  | @ -12,6 +12,7 @@ | |||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
| #include "consspaceobject.h" | ||||
| #include "conspage.h" | ||||
|  | @ -21,15 +22,52 @@ | |||
| #include "lispops.h" | ||||
| #include "print.h" | ||||
| #include "read.h" | ||||
| #include "real.h" | ||||
| #include "stack.h" | ||||
| 
 | ||||
| /*
 | ||||
| /**
 | ||||
|  * Add an indefinite number of numbers together | ||||
|  * @param env the evaluation environment - ignored; | ||||
|  * @param frame the stack frame. | ||||
|  * @return a pointer to an integer or real. | ||||
|  */ | ||||
| struct cons_pointer | ||||
| lisp_plus( struct cons_pointer s_expr, struct cons_pointer env, | ||||
|            struct stack_frame *frame ) { | ||||
|     struct cons_space_object cell = pointer2cell( s_expr ); | ||||
|     struct cons_space_object result = NIL; | ||||
| lisp_plus(struct stack_frame *frame, struct cons_pointer env) { | ||||
|     struct cons_pointer result = NIL; | ||||
|     long int i_accumulator = 0; | ||||
|     long double d_accumulator = 0; | ||||
|     bool is_int = true; | ||||
|      | ||||
|     for (int i = 0; i < args_in_frame && !nilp(frame->arg[i]); i++) { | ||||
|         struct cons_space_object arg = pointer2cell(frame->arg[i]); | ||||
|          | ||||
|         switch (arg.tag.value) { | ||||
|         case INTEGERTV: | ||||
|             i_accumulator += arg.payload.integer.value; | ||||
|             d_accumulator += numeric_value( frame->arg[i]); | ||||
|             break; | ||||
|         case REALTV: | ||||
|             d_accumulator += arg.payload.real.value; | ||||
|             is_int = false; | ||||
|         default: | ||||
|             lisp_throw( | ||||
|                     c_string_to_lisp_string("Cannot add: not a number"),  | ||||
|                                             frame); | ||||
|         } | ||||
|          | ||||
|         if (! nilp(frame->more)) { | ||||
|             lisp_throw( | ||||
|                     c_string_to_lisp_string("Cannot yet add more than 8 numbers"),  | ||||
|                                             frame); | ||||
|         } | ||||
| 
 | ||||
|         if ( is_int) { | ||||
|             result = make_integer( i_accumulator); | ||||
|         } else { | ||||
|             result = make_real( d_accumulator); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return result;     | ||||
| } | ||||
| */ | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										35
									
								
								src/peano.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/peano.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| /**
 | ||||
|  * peano.h | ||||
|  * | ||||
|  * Basic peano arithmetic | ||||
|  * | ||||
|  * (c) 2017 Simon Brooke <simon@journeyman.cc> | ||||
|  * Licensed under GPL version 2.0, or, at your option, any later version. | ||||
|  */ | ||||
| 
 | ||||
| #include "consspaceobject.h" | ||||
| 
 | ||||
| #ifndef PEANO_H | ||||
| #define PEANO_H | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  * Add an indefinite number of numbers together | ||||
|  * @param env the evaluation environment - ignored; | ||||
|  * @param frame the stack frame. | ||||
|  * @return a pointer to an integer or real. | ||||
|  */ | ||||
| struct cons_pointer | ||||
| lisp_plus(struct stack_frame *frame, struct cons_pointer env); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif /* PEANO_H */ | ||||
| 
 | ||||
							
								
								
									
										14
									
								
								src/read.c
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/read.c
									
									
									
									
									
								
							|  | @ -147,8 +147,6 @@ struct cons_pointer read_string( FILE * input, wint_t initial ) { | |||
|     struct cons_pointer cdr = NIL; | ||||
|     struct cons_pointer result; | ||||
| 
 | ||||
|     fwprintf( stderr, L"read_string starting '%C' (%d)\n", initial, initial ); | ||||
| 
 | ||||
|     switch ( initial ) { | ||||
|     case '\0': | ||||
|         result = make_string( initial, NIL ); | ||||
|  | @ -168,8 +166,6 @@ struct cons_pointer read_symbol( FILE * input, wint_t initial ) { | |||
|     struct cons_pointer cdr = NIL; | ||||
|     struct cons_pointer result; | ||||
| 
 | ||||
|     fwprintf( stderr, L"read_symbol starting '%C' (%d)\n", initial, initial ); | ||||
| 
 | ||||
|     switch ( initial ) { | ||||
|     case '\0': | ||||
|         result = make_symbol( initial, NIL ); | ||||
|  | @ -191,15 +187,15 @@ struct cons_pointer read_symbol( FILE * input, wint_t initial ) { | |||
|         ungetwc( initial, input ); | ||||
|         break; | ||||
|     default: | ||||
|         if ( iswblank( initial ) || !iswprint( initial ) ) { | ||||
|             result = make_symbol( '\0', NIL ); | ||||
|         if ( iswalnum( initial ) ) { | ||||
|             result = | ||||
|                 make_symbol( initial, read_symbol( input, fgetwc( input ) ) ); | ||||
|         } else { | ||||
|             result = NIL; | ||||
|             /*
 | ||||
|              * push back the character read | ||||
|              */ | ||||
|             ungetwc( initial, input ); | ||||
|         } else { | ||||
|             result = | ||||
|                 make_symbol( initial, read_symbol( input, fgetwc( input ) ) ); | ||||
|         }  | ||||
|         break; | ||||
|     } | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ extern "C" { | |||
|  * @param value the value to wrap; | ||||
|  * @return a real number cell wrapping this value. | ||||
|  */ | ||||
|     struct cons_pointer make_real( double value ); | ||||
| struct cons_pointer make_real( double value ); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|  |  | |||
							
								
								
									
										12
									
								
								src/repl.c
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/repl.c
									
									
									
									
									
								
							|  | @ -31,19 +31,13 @@ repl( FILE * in_stream, FILE * out_stream, FILE * error_stream, | |||
|         struct cons_pointer input = read( in_stream ); | ||||
|         fwprintf( error_stream, L"\nread {%d,%d}=> ", input.page, | ||||
|                   input.offset ); | ||||
|         if ( show_prompt ) { | ||||
|             fwprintf( out_stream, L"\n-> " ); | ||||
|         } | ||||
|         print( error_stream, input); | ||||
| 
 | ||||
|         /* OK, I think what's going wrong here is we're passing by 
 | ||||
|          * value and I think we should be passing by reference. | ||||
|          * I'm not certain about that, and as it will be a really | ||||
|          * major change I'm going to think some more before making | ||||
|          * in */ | ||||
|         print( out_stream, lisp_eval( input, oblist, NULL ) ); | ||||
|         struct cons_pointer value = lisp_eval( input, oblist, NULL ); | ||||
|         // print( out_stream, input );
 | ||||
|         fwprintf( out_stream, L"\n" ); | ||||
|         fwprintf( error_stream, L"\neval {%d,%d}=> ", input.page, | ||||
|                   input.offset ); | ||||
|         print( out_stream,  value); | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										62
									
								
								src/stack.c
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								src/stack.c
									
									
									
									
									
								
							|  | @ -23,6 +23,7 @@ | |||
| #include "consspaceobject.h" | ||||
| #include "conspage.h" | ||||
| #include "lispops.h" | ||||
| #include "print.h" | ||||
| #include "stack.h" | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -60,6 +61,8 @@ struct stack_frame *make_stack_frame( struct stack_frame *previous, | |||
|         struct cons_space_object cell = pointer2cell( args ); | ||||
| 
 | ||||
|         if ( i < args_in_frame ) { | ||||
|             fwprintf(stderr, L"Making frame; arg %d: ", i); | ||||
|             print(stderr, cell.payload.cons.car); | ||||
|             /*
 | ||||
|              * TODO: if we were running on real massively parallel hardware, | ||||
|              * each arg except the first should be handed off to another | ||||
|  | @ -69,6 +72,65 @@ struct stack_frame *make_stack_frame( struct stack_frame *previous, | |||
|             inc_ref( result->arg[i] ); | ||||
| 
 | ||||
|             args = cell.payload.cons.cdr; | ||||
|             i++; | ||||
|         } else { | ||||
|             /*
 | ||||
|              * TODO: this isn't right. These args should also each be evaled. | ||||
|              */ | ||||
|             result->more = args; | ||||
|             inc_ref( result->more ); | ||||
| 
 | ||||
|             args = NIL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * A 'special' frame is exactly like a normal stack frame except that the | ||||
|  * arguments are unevaluated. | ||||
|  * @param previous the previous stack frame; | ||||
|  * @param args a list of the arguments to be stored in this stack frame; | ||||
|  * @param env the execution environment; | ||||
|  * @return a new special frame. | ||||
|  */ | ||||
| struct stack_frame *make_special_frame( struct stack_frame *previous, | ||||
|                                       struct cons_pointer args, | ||||
|                                       struct cons_pointer env ) { | ||||
|     /*
 | ||||
|      * TODO: later, pop a frame off a free-list of stack frames | ||||
|      */ | ||||
|     struct stack_frame *result = malloc( sizeof( struct stack_frame ) ); | ||||
| 
 | ||||
|     result->previous = previous; | ||||
| 
 | ||||
|     /*
 | ||||
|      * clearing the frame with memset would probably be slightly quicker, but | ||||
|      * this is clear. | ||||
|      */ | ||||
|     result->more = NIL; | ||||
|     result->function = NIL; | ||||
| 
 | ||||
|     for ( int i = 0; i < args_in_frame; i++ ) { | ||||
|         result->arg[i] = NIL; | ||||
|     } | ||||
| 
 | ||||
|     int i = 0;                  /* still an index into args, so same name will
 | ||||
|                                  * do */ | ||||
| 
 | ||||
|     while ( !nilp( args ) ) {   /* iterate down the arg list filling in the
 | ||||
|                                  * arg slots in the frame. When there are no | ||||
|                                  * more slots, if there are still args, stash | ||||
|                                  * them on more */ | ||||
|         struct cons_space_object cell = pointer2cell( args ); | ||||
| 
 | ||||
|         if ( i < args_in_frame ) { | ||||
|             result->arg[i] = cell.payload.cons.car; | ||||
|             inc_ref( result->arg[i] ); | ||||
| 
 | ||||
|             args = cell.payload.cons.cdr; | ||||
|             i++; | ||||
|         } else { | ||||
|             /*
 | ||||
|              * TODO: this isn't right. These args should also each be evaled. | ||||
|  |  | |||
							
								
								
									
										12
									
								
								src/stack.h
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/stack.h
									
									
									
									
									
								
							|  | @ -30,6 +30,18 @@ struct stack_frame *make_stack_frame( struct stack_frame *previous, | |||
| void free_stack_frame( struct stack_frame *frame ); | ||||
| struct cons_pointer fetch_arg( struct stack_frame *frame, unsigned int n ); | ||||
| 
 | ||||
| /**
 | ||||
|  * A 'special' frame is exactly like a normal stack frame except that the  | ||||
|  * arguments are unevaluated. | ||||
|  * @param previous the previous stack frame; | ||||
|  * @param args a list of the arguments to be stored in this stack frame; | ||||
|  * @param env the execution environment; | ||||
|  * @return a new special frame. | ||||
|  */ | ||||
| struct stack_frame *make_special_frame( struct stack_frame *previous, | ||||
|                                       struct cons_pointer args, | ||||
|                                       struct cons_pointer env ); | ||||
| 
 | ||||
| /*
 | ||||
|  * struct stack_frame is defined in consspaceobject.h to break circularity | ||||
|  * TODO: refactor.  | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| #!/bin/bash | ||||
| 
 | ||||
| expected='(1 2 3 ("Fred") nil 77354)' | ||||
| actual=`echo '(1 2 3 ("Fred") () 77354)' | target/psse 2> /dev/null | head -1` | ||||
| actual=`echo "'(1 2 3 (\"Fred\") () 77354)" | target/psse 2> /dev/null | head -1` | ||||
| 
 | ||||
| if [ "${expected}" = "${actual}" ] | ||||
| then | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| #!/bin/bash | ||||
| 
 | ||||
| expected='(quote Fred)' | ||||
| expected='Fred' | ||||
| actual=`echo "'Fred" | target/psse 2> /dev/null | head -1` | ||||
| 
 | ||||
| if [ "${expected}" = "${actual}" ] | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| #!/bin/bash | ||||
| 
 | ||||
| expected='(quote (123 (4 (5 nil)) Fred))' | ||||
| expected='(123 (4 (5 nil)) Fred)' | ||||
| actual=`echo "'(123 (4 (5 ())) Fred)" | target/psse 2> /dev/null | head -1` | ||||
| 
 | ||||
| if [ "${expected}" = "${actual}" ] | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| #!/bin/bash | ||||
| 
 | ||||
| expected="(1 2 3)" | ||||
| actual=`echo '(1 2 3)' | target/psse 2> /dev/null | head -1` | ||||
| actual=`echo "'(1 2 3)" | target/psse 2> /dev/null | head -1` | ||||
| 
 | ||||
| if [ "${expected}" = "${actual}" ] | ||||
| then | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue