Debugging support (Stop, Step, Breakpoint)

Jul 22, 2011 at 2:44 PM

The engine runs in the same thread that it was created on. This seems obvious, but...

How can I run a script, and keep the GUI responsive? Please consider that the script might be calling methods clr objects that raise changes in the gui.

This also impacts support for stopping the engine while the script is running. Or the step event, do not return to keep executing until the user press the button to continue.

I cannot think of an easy solution.

Any suggestions?

Please?

Jul 22, 2011 at 4:27 PM

I came up with an implementation... it works, but is so crappy... any better ideas?

        enum eEngineExec {
            Run,
            Stop,
            Step,
            Continue,
            Breakpoint
        }

        enum eEngineFlow {
            Stopped,
            Running,
            Stepping,
            Breakpointing
        }

        eEngineExec engineStatus = eEngineExec.Stop;
        eEngineFlow engineFlow = eEngineFlow.Stopped;
        int CurrentExecutionLine = -1;
        Jint.JintEngine engine;

        HashSet<int> BreakpointsLineId = new HashSet<int>( );

        public void RunScript( ) {
            engine.SetDebugMode( false );
            engineFlow = eEngineFlow.Running;
            Run( );
        }

        public void DebugScript( object param ) {
            if ( engineFlow == eEngineFlow.Stopped ) {
                engine.SetDebugMode( true );
                engineFlow = eEngineFlow.Breakpointing;
                engineStatus = eEngineExec.Breakpoint;
                Run( );
            }
            else {
                engineFlow = eEngineFlow.Breakpointing;
                engineStatus = eEngineExec.Continue;
            }
        }

        public void StepIn( object param ) {
            if ( engineFlow == eEngineFlow.Stopped ) {
                engineStatus = eEngineExec.Step;
                engineFlow = eEngineFlow.Stepping;
                engine.SetDebugMode( true );
                Run( );
            }
            else {
                engineStatus = eEngineExec.Continue;
                engineFlow = eEngineFlow.Stepping;
            }
        }

        public void Stop( object param ) {
            engineStatus = eEngineExec.Stop;
        }

        void Run( ) {
            try {
                engine.Run( editor.txtScript.Text );
                Program.App.Scripts( null );
                CurrentExecutionLine = -1;
                engineFlow = eEngineFlow.Stopped;
            }
            catch ( Exception ex ) {
                if ( !ex.Message.Contains( "StopEngineException" ) ) {
                    MessageBox.Show( ex.Message, "Script Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation );
                }
            }
        }

        void engine_Step( object sender, Jint.Debugger.DebugInformation e ) {

            CurrentExecutionLine = e.CurrentStatement.Source.Start.Line - 1;
            editor.txtScript.Selection.Start = new Place( 0, CurrentExecutionLine );
            editor.txtScript.DoSelectionVisible( );

            ShowStack( e.CallStack, e.CurrentStatement.Source.Code );
            ShowLocals( e.Locals );

            while ( engineStatus == eEngineExec.Step || ( engineStatus == eEngineExec.Breakpoint && BreakpointsLineId.Contains( CurrentExecutionLine ) ) ) {
                Application.DoEvents( );
            }

            if ( engineStatus == eEngineExec.Continue ) {
                if ( engineFlow == eEngineFlow.Stepping ) {
                    engineStatus = eEngineExec.Step;
                }
                else if ( engineFlow == eEngineFlow.Breakpointing ) {
                    engineStatus = eEngineExec.Breakpoint;
                }
            }

            if ( engineStatus == eEngineExec.Stop )
                throw new StopEngineException( );
        }

        class StopEngineException: Exception { }

Coordinator
Jul 22, 2011 at 5:19 PM

Are you trying to do a debug UI ?

Jul 22, 2011 at 6:13 PM

yup, almost done actually. It is build into a component so later can be released as open project (or merged with the jint solution). I am using the FastColoredTextBox as editor.

There are more things I would need for a more complete implementation, like the ability to parse the string so the list of variables and contexts get populated, but the script it self is not executed, this will help me with the intellisense.

Any pointers?

Coordinator
Jul 22, 2011 at 6:23 PM

I don't understand what strings you want to parse.

Coordinator
Jul 22, 2011 at 6:25 PM

By the way I would love to use it, as it could help me to fix some Jint bugs in specific scripts. Dare sharing a pre-release ?

Jul 22, 2011 at 6:28 PM

I meant the script.

If the users is typing:

var dt = new Sytem.Data.DataTable( );

dt.

When the user types the dot, then all the appropiate methods and properties should pop up. For that the script needs to be parsed but not executed, and be more resilient to parse errors so it build the AST (and I have access to it) without rejecting it.

Jul 22, 2011 at 6:29 PM

And yes, no problem, I would share.

Jul 22, 2011 at 7:24 PM

How do I send it directly to you?

Coordinator
Jul 22, 2011 at 7:30 PM

Dropbox, or ping me on my codeplex profile, I'll answer to it. 

Coordinator
Jul 22, 2011 at 7:32 PM

If I were you I would not even try to provide intellisense for such a dynamic language. Instead you could focus on providing great dynamic toosl, like live variable information, and cutom expression evaluation.

Jul 22, 2011 at 7:43 PM

Well... Some intellisense is better than none, so I'll try. Live variable value is somewhat in place in the JintEditor, custom expression evaluator... It is a good idea, but some of the api that I expose to the script is DB oriented, so I need to think how to protect the DB from the user script, I'll chew on it. Does jint creates an AST? how close is to the DLR AST?

Coordinator
Jul 22, 2011 at 7:47 PM

It creates an AST, lokk at the Compile method on JintEngine. This AST is evaluated dynamically by the ExecutionVisitor. But in order to be constructed this must be a valid program. You can also generate others parts of the AST independently, like a function, an expression, ...

Jul 22, 2011 at 7:53 PM

"But in order to be constructed this must be a valid program" that is exactly what I was worry about. I am not sure how Antlr handles incomplete trees. I'll take a look at it.

Jul 22, 2011 at 7:57 PM

Any chance to implement the debugger keyword? just raise the OnStep when it finds it.

Coordinator
Jul 22, 2011 at 8:19 PM

You'd better implement it by yourself, which would call OnStep by reflection. Should be very easy for you.

Jul 22, 2011 at 8:22 PM

Thanks for the compliment... I didn't wanted to touch the antlr file and maintain a forked version of jint, so if I do it, would it be a change that you would be interested in merge in the trunk?

Coordinator
Jul 22, 2011 at 8:53 PM

No, I meant that during the engine initialization you could add a new function named “debug” like you did for alert and write in the editor, which would call whatever you want on the engine, by reflection.

Jul 25, 2011 at 2:36 PM

I actually tried your approach:

            engine.SetFunction( "debugger", new Action( ( ) => {
                if ( engineFlow == eEngineFlow.Breakpointing || engineFlow == eEngineFlow.Stepping ) {
                    engineStatus = eEngineExec.Step;
                }
            } ) );

But as soon as I define the function, the engine refuses to call the Step event. Commentde only those 5 lines, and the debugger works fine. You have the editor, you can try.

Besides... it is a keyword, and this is a hack.

I am thinking on making available the editor in codeplex... How do you feel about distributing the jint.dll with it? How do you feel about creating a link from here?