2023-01-10 15:26:54 +01:00
/**************************************************************************/
/* script_debugger_local.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-10 02:10:30 +01:00
# include "script_debugger_local.h"
2017-01-16 08:04:19 +01:00
2018-09-11 18:13:45 +02:00
# include "core/os/os.h"
# include "scene/main/scene_tree.h"
2014-02-10 02:10:30 +01:00
2019-07-29 20:09:22 +02:00
void ScriptDebuggerLocal : : debug ( ScriptLanguage * p_script , bool p_can_continue , bool p_is_error_breakpoint ) {
2018-02-11 12:08:37 +01:00
if ( ! target_function . empty ( ) ) {
String current_function = p_script - > debug_get_stack_level_function ( 0 ) ;
if ( current_function ! = target_function ) {
set_depth ( 0 ) ;
set_lines_left ( 1 ) ;
return ;
}
target_function = " " ;
}
print_line ( " \n Debugger Break, Reason: ' " + p_script - > debug_get_error ( ) + " ' " ) ;
2017-03-05 16:44:50 +01:00
print_line ( " *Frame " + itos ( 0 ) + " - " + p_script - > debug_get_stack_level_source ( 0 ) + " : " + itos ( p_script - > debug_get_stack_level_line ( 0 ) ) + " in function ' " + p_script - > debug_get_stack_level_function ( 0 ) + " ' " ) ;
2014-02-10 02:10:30 +01:00
print_line ( " Enter \" help \" for assistance. " ) ;
2017-03-05 16:44:50 +01:00
int current_frame = 0 ;
int total_frames = p_script - > debug_get_stack_level_count ( ) ;
while ( true ) {
2014-02-10 02:10:30 +01:00
OS : : get_singleton ( ) - > print ( " debug> " ) ;
String line = OS : : get_singleton ( ) - > get_stdin_string ( ) . strip_edges ( ) ;
2018-02-11 12:08:37 +01:00
// Cache options
String variable_prefix = options [ " variable_prefix " ] ;
2017-03-05 16:44:50 +01:00
if ( line = = " " ) {
2018-02-11 12:08:37 +01:00
print_line ( " \n Debugger Break, Reason: ' " + p_script - > debug_get_error ( ) + " ' " ) ;
2017-03-05 16:44:50 +01:00
print_line ( " *Frame " + itos ( current_frame ) + " - " + p_script - > debug_get_stack_level_source ( current_frame ) + " : " + itos ( p_script - > debug_get_stack_level_line ( current_frame ) ) + " in function ' " + p_script - > debug_get_stack_level_function ( current_frame ) + " ' " ) ;
2014-02-10 02:10:30 +01:00
print_line ( " Enter \" help \" for assistance. " ) ;
2021-05-05 12:44:11 +02:00
} else if ( line = = " c " | | line = = " continue " ) {
2014-02-10 02:10:30 +01:00
break ;
2021-05-05 12:44:11 +02:00
} else if ( line = = " bt " | | line = = " breakpoint " ) {
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < total_frames ; i + + ) {
String cfi = ( current_frame = = i ) ? " * " : " " ; //current frame indicator
print_line ( cfi + " Frame " + itos ( i ) + " - " + p_script - > debug_get_stack_level_source ( i ) + " : " + itos ( p_script - > debug_get_stack_level_line ( i ) ) + " in function ' " + p_script - > debug_get_stack_level_function ( i ) + " ' " ) ;
2014-02-10 02:10:30 +01:00
}
} else if ( line . begins_with ( " fr " ) | | line . begins_with ( " frame " ) ) {
2017-03-05 16:44:50 +01:00
if ( line . get_slice_count ( " " ) = = 1 ) {
print_line ( " *Frame " + itos ( current_frame ) + " - " + p_script - > debug_get_stack_level_source ( current_frame ) + " : " + itos ( p_script - > debug_get_stack_level_line ( current_frame ) ) + " in function ' " + p_script - > debug_get_stack_level_function ( current_frame ) + " ' " ) ;
2014-02-10 02:10:30 +01:00
} else {
2017-03-05 16:44:50 +01:00
int frame = line . get_slicec ( ' ' , 1 ) . to_int ( ) ;
if ( frame < 0 | | frame > = total_frames ) {
2014-02-10 02:10:30 +01:00
print_line ( " Error: Invalid frame. " ) ;
} else {
2017-03-05 16:44:50 +01:00
current_frame = frame ;
print_line ( " *Frame " + itos ( frame ) + " - " + p_script - > debug_get_stack_level_source ( frame ) + " : " + itos ( p_script - > debug_get_stack_level_line ( frame ) ) + " in function ' " + p_script - > debug_get_stack_level_function ( frame ) + " ' " ) ;
2014-02-10 02:10:30 +01:00
}
}
2018-02-11 12:08:37 +01:00
} else if ( line . begins_with ( " set " ) ) {
if ( line . get_slice_count ( " " ) = = 1 ) {
for ( Map < String , String > : : Element * E = options . front ( ) ; E ; E = E - > next ( ) ) {
print_line ( " \t " + E - > key ( ) + " = " + E - > value ( ) ) ;
}
} else {
String key_value = line . get_slicec ( ' ' , 1 ) ;
int value_pos = key_value . find ( " = " ) ;
if ( value_pos < 0 ) {
print_line ( " Error: Invalid set format. Use: set key=value " ) ;
} else {
String key = key_value . left ( value_pos ) ;
if ( ! options . has ( key ) ) {
print_line ( " Error: Unknown option " + key ) ;
} else {
// Allow explicit tab character
String value = key_value . right ( value_pos + 1 ) . replace ( " \\ t " , " \t " ) ;
options [ key ] = value ;
}
}
}
2017-03-05 16:44:50 +01:00
} else if ( line = = " lv " | | line = = " locals " ) {
2014-02-10 02:10:30 +01:00
List < String > locals ;
List < Variant > values ;
2017-03-05 16:44:50 +01:00
p_script - > debug_get_stack_level_locals ( current_frame , & locals , & values ) ;
2018-02-11 12:08:37 +01:00
print_variables ( locals , values , variable_prefix ) ;
2014-02-10 02:10:30 +01:00
2017-03-05 16:44:50 +01:00
} else if ( line = = " gv " | | line = = " globals " ) {
2018-02-11 12:08:37 +01:00
List < String > globals ;
2014-02-10 02:10:30 +01:00
List < Variant > values ;
2018-02-11 12:08:37 +01:00
p_script - > debug_get_globals ( & globals , & values ) ;
print_variables ( globals , values , variable_prefix ) ;
2014-02-10 02:10:30 +01:00
2017-03-05 16:44:50 +01:00
} else if ( line = = " mv " | | line = = " members " ) {
2018-02-11 12:08:37 +01:00
List < String > members ;
2014-02-10 02:10:30 +01:00
List < Variant > values ;
2018-02-11 12:08:37 +01:00
p_script - > debug_get_stack_level_members ( current_frame , & members , & values ) ;
print_variables ( members , values , variable_prefix ) ;
2014-02-10 02:10:30 +01:00
} else if ( line . begins_with ( " p " ) | | line . begins_with ( " print " ) ) {
2017-03-05 16:44:50 +01:00
if ( line . get_slice_count ( " " ) < = 1 ) {
2014-02-10 02:10:30 +01:00
print_line ( " Usage: print <expre> " ) ;
} else {
2017-03-05 16:44:50 +01:00
String expr = line . get_slicec ( ' ' , 2 ) ;
String res = p_script - > debug_parse_stack_level_expression ( current_frame , expr ) ;
2014-02-10 02:10:30 +01:00
print_line ( res ) ;
}
2017-03-05 16:44:50 +01:00
} else if ( line = = " s " | | line = = " step " ) {
2014-02-10 02:10:30 +01:00
set_depth ( - 1 ) ;
set_lines_left ( 1 ) ;
break ;
2018-02-11 12:08:37 +01:00
} else if ( line = = " n " | | line = = " next " ) {
2014-02-10 02:10:30 +01:00
set_depth ( 0 ) ;
set_lines_left ( 1 ) ;
break ;
2018-02-11 12:08:37 +01:00
} else if ( line = = " fin " | | line = = " finish " ) {
String current_function = p_script - > debug_get_stack_level_function ( 0 ) ;
for ( int i = 0 ; i < total_frames ; i + + ) {
target_function = p_script - > debug_get_stack_level_function ( i ) ;
if ( target_function ! = current_function ) {
set_depth ( 0 ) ;
set_lines_left ( 1 ) ;
return ;
}
}
print_line ( " Error: Reached last frame. " ) ;
target_function = " " ;
2014-02-10 02:10:30 +01:00
} else if ( line . begins_with ( " br " ) | | line . begins_with ( " break " ) ) {
2017-03-05 16:44:50 +01:00
if ( line . get_slice_count ( " " ) < = 1 ) {
2021-05-04 14:20:36 +02:00
const Map < int , Set < StringName > > & breakpoints = get_breakpoints ( ) ;
2018-02-11 12:08:37 +01:00
if ( breakpoints . size ( ) = = 0 ) {
print_line ( " No Breakpoints. " ) ;
continue ;
}
print_line ( " Breakpoint(s): " + itos ( breakpoints . size ( ) ) ) ;
2021-05-04 14:20:36 +02:00
for ( Map < int , Set < StringName > > : : Element * E = breakpoints . front ( ) ; E ; E = E - > next ( ) ) {
2018-02-11 12:08:37 +01:00
print_line ( " \t " + String ( E - > value ( ) . front ( ) - > get ( ) ) + " : " + itos ( E - > key ( ) ) ) ;
}
2014-02-10 02:10:30 +01:00
} else {
2018-02-11 12:08:37 +01:00
Pair < String , int > breakpoint = to_breakpoint ( line ) ;
String source = breakpoint . first ;
int linenr = breakpoint . second ;
2014-02-10 02:10:30 +01:00
2021-05-05 12:44:11 +02:00
if ( source . empty ( ) ) {
2018-02-11 12:08:37 +01:00
continue ;
2021-05-05 12:44:11 +02:00
}
2014-02-10 02:10:30 +01:00
2018-02-11 12:08:37 +01:00
insert_breakpoint ( linenr , source ) ;
2014-02-10 02:10:30 +01:00
2018-02-11 12:08:37 +01:00
print_line ( " Added breakpoint at " + source + " : " + itos ( linenr ) ) ;
2014-02-10 02:10:30 +01:00
}
2018-02-11 12:08:37 +01:00
} else if ( line = = " q " | | line = = " quit " ) {
// Do not stop again on quit
clear_breakpoints ( ) ;
ScriptDebugger : : get_singleton ( ) - > set_depth ( - 1 ) ;
ScriptDebugger : : get_singleton ( ) - > set_lines_left ( - 1 ) ;
SceneTree : : get_singleton ( ) - > quit ( ) ;
break ;
2014-02-10 02:10:30 +01:00
} else if ( line . begins_with ( " delete " ) ) {
2017-03-05 16:44:50 +01:00
if ( line . get_slice_count ( " " ) < = 1 ) {
2014-02-10 02:10:30 +01:00
clear_breakpoints ( ) ;
} else {
2018-02-11 12:08:37 +01:00
Pair < String , int > breakpoint = to_breakpoint ( line ) ;
String source = breakpoint . first ;
int linenr = breakpoint . second ;
2014-02-10 02:10:30 +01:00
2021-05-05 12:44:11 +02:00
if ( source . empty ( ) ) {
2018-02-11 12:08:37 +01:00
continue ;
2021-05-05 12:44:11 +02:00
}
2014-02-10 02:10:30 +01:00
2018-02-11 12:08:37 +01:00
remove_breakpoint ( linenr , source ) ;
2014-02-10 02:10:30 +01:00
2018-02-11 12:08:37 +01:00
print_line ( " Removed breakpoint at " + source + " : " + itos ( linenr ) ) ;
2014-02-10 02:10:30 +01:00
}
2017-03-05 16:44:50 +01:00
} else if ( line = = " h " | | line = = " help " ) {
2014-02-10 02:10:30 +01:00
print_line ( " Built-In Debugger command list: \n " ) ;
2018-02-11 12:08:37 +01:00
print_line ( " \t c,continue \t \t Continue execution. " ) ;
print_line ( " \t bt,backtrace \t \t Show stack trace (frames). " ) ;
2014-02-10 02:10:30 +01:00
print_line ( " \t fr,frame <frame>: \t Change current frame. " ) ;
2018-02-11 12:08:37 +01:00
print_line ( " \t lv,locals \t \t Show local variables for current frame. " ) ;
print_line ( " \t mv,members \t \t Show member variables for \" this \" in frame. " ) ;
print_line ( " \t gv,globals \t \t Show global variables. " ) ;
print_line ( " \t p,print <expr> \t \t Execute and print variable in expression. " ) ;
print_line ( " \t s,step \t \t \t Step to next line. " ) ;
print_line ( " \t n,next \t \t \t Next line. " ) ;
print_line ( " \t fin,finish \t \t Step out of current frame. " ) ;
print_line ( " \t br,break [source:line] \t List all breakpoints or place a breakpoint. " ) ;
print_line ( " \t delete [source:line]: \t Delete one/all breakpoints. " ) ;
print_line ( " \t set [key=value]: \t List all options, or set one. " ) ;
print_line ( " \t q,quit \t \t \t Quit application. " ) ;
2014-02-10 02:10:30 +01:00
} else {
print_line ( " Error: Invalid command, enter \" help \" for assistance. " ) ;
}
}
}
2018-02-11 12:08:37 +01:00
void ScriptDebuggerLocal : : print_variables ( const List < String > & names , const List < Variant > & values , const String & variable_prefix ) {
String value ;
Vector < String > value_lines ;
const List < Variant > : : Element * V = values . front ( ) ;
for ( const List < String > : : Element * E = names . front ( ) ; E ; E = E - > next ( ) ) {
value = String ( V - > get ( ) ) ;
if ( variable_prefix . empty ( ) ) {
print_line ( E - > get ( ) + " : " + String ( V - > get ( ) ) ) ;
} else {
print_line ( E - > get ( ) + " : " ) ;
value_lines = value . split ( " \n " ) ;
for ( int i = 0 ; i < value_lines . size ( ) ; + + i ) {
print_line ( variable_prefix + value_lines [ i ] ) ;
}
}
V = V - > next ( ) ;
}
}
Pair < String , int > ScriptDebuggerLocal : : to_breakpoint ( const String & p_line ) {
String breakpoint_part = p_line . get_slicec ( ' ' , 1 ) ;
Pair < String , int > breakpoint ;
int last_colon = breakpoint_part . rfind ( " : " ) ;
if ( last_colon < 0 ) {
print_line ( " Error: Invalid breakpoint format. Expected [source:line] " ) ;
return breakpoint ;
}
breakpoint . first = breakpoint_find_source ( breakpoint_part . left ( last_colon ) . strip_edges ( ) ) ;
breakpoint . second = breakpoint_part . right ( last_colon ) . strip_edges ( ) . to_int ( ) ;
return breakpoint ;
}
2016-05-22 02:18:16 +02:00
struct _ScriptDebuggerLocalProfileInfoSort {
2017-03-05 16:44:50 +01:00
bool operator ( ) ( const ScriptLanguage : : ProfilingInfo & A , const ScriptLanguage : : ProfilingInfo & B ) const {
2016-05-22 02:18:16 +02:00
return A . total_time > B . total_time ;
}
} ;
2022-05-06 19:28:55 +02:00
void ScriptDebuggerLocal : : profiling_set_frame_times ( float p_frame_time , float p_process_time , float p_physics_time , float p_physics_frame_time ) {
2017-03-05 16:44:50 +01:00
frame_time = p_frame_time ;
2022-05-06 19:28:55 +02:00
process_time = p_process_time ;
2017-09-30 16:19:07 +02:00
physics_time = p_physics_time ;
physics_frame_time = p_physics_frame_time ;
2016-05-22 02:18:16 +02:00
}
void ScriptDebuggerLocal : : idle_poll ( ) {
2021-05-05 12:44:11 +02:00
if ( ! profiling ) {
2016-05-22 02:18:16 +02:00
return ;
2021-05-05 12:44:11 +02:00
}
2016-05-22 02:18:16 +02:00
uint64_t diff = OS : : get_singleton ( ) - > get_ticks_usec ( ) - idle_accum ;
2021-05-05 12:44:11 +02:00
if ( diff < 1000000 ) { //show every one second
2016-05-22 02:18:16 +02:00
return ;
2021-05-05 12:44:11 +02:00
}
2016-05-22 02:18:16 +02:00
idle_accum = OS : : get_singleton ( ) - > get_ticks_usec ( ) ;
2017-03-05 16:44:50 +01:00
int ofs = 0 ;
for ( int i = 0 ; i < ScriptServer : : get_language_count ( ) ; i + + ) {
2018-07-25 03:11:03 +02:00
ofs + = ScriptServer : : get_language ( i ) - > profiling_get_frame_data ( & pinfo . write [ ofs ] , pinfo . size ( ) - ofs ) ;
2016-05-22 02:18:16 +02:00
}
2017-03-05 16:44:50 +01:00
SortArray < ScriptLanguage : : ProfilingInfo , _ScriptDebuggerLocalProfileInfoSort > sort ;
2017-11-25 04:07:54 +01:00
sort . sort ( pinfo . ptrw ( ) , ofs ) ;
2016-05-22 02:18:16 +02:00
//falta el frame time
2017-03-05 16:44:50 +01:00
uint64_t script_time_us = 0 ;
2016-05-22 02:18:16 +02:00
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < ofs ; i + + ) {
script_time_us + = pinfo [ i ] . self_time ;
2016-05-22 02:18:16 +02:00
}
2017-03-05 16:44:50 +01:00
float script_time = USEC_TO_SEC ( script_time_us ) ;
2016-05-22 02:18:16 +02:00
2017-03-05 16:44:50 +01:00
float total_time = frame_time ;
2016-05-22 02:18:16 +02:00
//print script total
2017-03-05 16:44:50 +01:00
print_line ( " FRAME: total: " + rtos ( frame_time ) + " script: " + rtos ( script_time ) + " / " + itos ( script_time * 100 / total_time ) + " % " ) ;
2016-05-22 02:18:16 +02:00
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < ofs ; i + + ) {
print_line ( itos ( i ) + " : " + pinfo [ i ] . signature ) ;
float tt = USEC_TO_SEC ( pinfo [ i ] . total_time ) ;
float st = USEC_TO_SEC ( pinfo [ i ] . self_time ) ;
print_line ( " \t total: " + rtos ( tt ) + " / " + itos ( tt * 100 / total_time ) + " % \t self: " + rtos ( st ) + " / " + itos ( st * 100 / total_time ) + " % tcalls: " + itos ( pinfo [ i ] . call_count ) ) ;
2016-05-22 02:18:16 +02:00
}
}
void ScriptDebuggerLocal : : profiling_start ( ) {
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < ScriptServer : : get_language_count ( ) ; i + + ) {
2016-05-22 02:18:16 +02:00
ScriptServer : : get_language ( i ) - > profiling_start ( ) ;
}
print_line ( " BEGIN PROFILING " ) ;
2017-03-05 16:44:50 +01:00
profiling = true ;
2016-05-22 02:18:16 +02:00
pinfo . resize ( 32768 ) ;
2017-03-05 16:44:50 +01:00
frame_time = 0 ;
2017-09-30 16:19:07 +02:00
physics_time = 0 ;
2022-05-06 19:28:55 +02:00
process_time = 0 ;
2017-09-30 16:19:07 +02:00
physics_frame_time = 0 ;
2016-05-22 02:18:16 +02:00
}
void ScriptDebuggerLocal : : profiling_end ( ) {
2017-03-05 16:44:50 +01:00
int ofs = 0 ;
2016-05-22 02:18:16 +02:00
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < ScriptServer : : get_language_count ( ) ; i + + ) {
2018-07-25 03:11:03 +02:00
ofs + = ScriptServer : : get_language ( i ) - > profiling_get_accumulated_data ( & pinfo . write [ ofs ] , pinfo . size ( ) - ofs ) ;
2016-05-22 02:18:16 +02:00
}
2017-03-05 16:44:50 +01:00
SortArray < ScriptLanguage : : ProfilingInfo , _ScriptDebuggerLocalProfileInfoSort > sort ;
2017-11-25 04:07:54 +01:00
sort . sort ( pinfo . ptrw ( ) , ofs ) ;
2016-05-22 02:18:16 +02:00
2017-03-05 16:44:50 +01:00
uint64_t total_us = 0 ;
for ( int i = 0 ; i < ofs ; i + + ) {
total_us + = pinfo [ i ] . self_time ;
2016-05-22 02:18:16 +02:00
}
2017-03-05 16:44:50 +01:00
float total_time = total_us / 1000000.0 ;
2016-05-22 02:18:16 +02:00
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < ofs ; i + + ) {
print_line ( itos ( i ) + " : " + pinfo [ i ] . signature ) ;
float tt = USEC_TO_SEC ( pinfo [ i ] . total_time ) ;
float st = USEC_TO_SEC ( pinfo [ i ] . self_time ) ;
print_line ( " \t total_ms: " + rtos ( tt ) + " \t self_ms: " + rtos ( st ) + " total%: " + itos ( tt * 100 / total_time ) + " \t self%: " + itos ( st * 100 / total_time ) + " \t calls: " + itos ( pinfo [ i ] . call_count ) ) ;
2016-05-22 02:18:16 +02:00
}
2017-03-05 16:44:50 +01:00
for ( int i = 0 ; i < ScriptServer : : get_language_count ( ) ; i + + ) {
2016-05-22 02:18:16 +02:00
ScriptServer : : get_language ( i ) - > profiling_stop ( ) ;
}
2017-03-05 16:44:50 +01:00
profiling = false ;
2016-05-22 02:18:16 +02:00
}
2017-03-05 16:44:50 +01:00
void ScriptDebuggerLocal : : send_message ( const String & p_message , const Array & p_args ) {
2018-02-05 11:18:49 +01:00
// This needs to be cleaned up entirely.
// print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args)));
2014-02-10 02:10:30 +01:00
}
2018-01-09 17:19:03 +01:00
void ScriptDebuggerLocal : : send_error ( const String & p_func , const String & p_file , int p_line , const String & p_err , const String & p_descr , ErrorHandlerType p_type , const Vector < ScriptLanguage : : StackInfo > & p_stack_info ) {
print_line ( " ERROR: ' " + ( p_descr . empty ( ) ? p_err : p_descr ) + " ' " ) ;
}
2014-02-10 02:10:30 +01:00
ScriptDebuggerLocal : : ScriptDebuggerLocal ( ) {
2017-03-05 16:44:50 +01:00
profiling = false ;
idle_accum = OS : : get_singleton ( ) - > get_ticks_usec ( ) ;
2018-02-11 12:08:37 +01:00
options [ " variable_prefix " ] = " " ;
2014-02-10 02:10:30 +01:00
}