Implementing Goto
- Posted by David Cuny <dcuny at LANSET.COM> Feb 12, 2002
- 522 views
Here's an attempt to implement a GOTO statement in Euphoria. It doesn't work (yet), but it's a start. Hopefully people can help me out here. I haven't seen the latest release of the Euphoria source, so perhaps there are some answers in there, too. The grammar is: LABEL <label> GOTO <label> To keep things simple in this example, I'll only allow one label and one forward reference. Here's an example: puts( 1, "this should execute" ) goto jump_here puts( 1, "this shouldn't execute" ) label jump_here OK, on to the code. The first thing that needs to be done is to define 'label' and 'goto' as Euphoria keywords. This is pretty easy. 1. Create the LABEL and GOTO token in the reserved words list. Add the following to RESWORDS.H after the definition of NAMESPACE: #define LABEL 524 #define GOTO 525 2. Add the keywords "label" and "goto" to the keyword list. Go to KEYLIST.H and in the keylist struct, add after "platform": {"label", S_KEYWORD, LABEL, 0, 0, 0}, {"goto", S_KEYWORD, GOTO, 0, 0, 0}, Now that the keywords are defined, we need to implement the grammar. Statements are implemented in PARSER.C in two places - parser() and Statement_list(). parser() is used to implement command level statements. For no compelling reason, we'll only allow 'goto' to be defined in functions and procedures. 3. Add the following to the case statement in parser() in PARSER.C: case LABEL: CompilerErr("label only allowed in function or procedure"); break; case GOTO: CompilerErr("goto only allowed in function or procedure"); break; 4. Add the following to Statement_list() in PARSER.C: case LABEL: StartSourceLine(TRUE); Label_statement(); break; case GOTO: StartSourceLine(TRUE); Goto_statement(); break; For the first pass, I'll implement dummy versions that parse, but don't do anything: 5. Here's a dummy version of the code to parse the label grammar. It needs to be added in PARSER.C before the definition of Statement_list(). The 'label' has already been parsed by Statement_list(), so we just need to read the label that follows. This is done by token(). If token() can't find the token in the table, it creates it as a variable: static void Label_statement() /* parse a label statement */ { TOKEN label; /* read the label */ token( label ); if (label.id != VARIABLE) { CompileErr("a label name is expected here"); } } 6. The dummy version of 'goto' has the same grammar - read the label that follows: static void Goto_statement() /* parse a goto statement */ { TOKEN label; /* read the label */ token( label ); if (label.id != VARIABLE) { CompileErr("a label name is expected here"); } } Compile the code, and you have a version Euphoria that supports the 'goto' grammar. It doesn't actually do anything, but it doesn't crash, either. So it's a good start. Time to get to the more speculative part - the part that *does* crash. Looking through do_exec (in EXECUTE.C), I tried to find an opcode that performs an unconditional jump. L_EXIT looks like it'll do (actually, L_EXIT, L_ENDWHILE, L_ELSE and L_SPLIT_ELSE look like they do the same thing), so I figure something like this: emit( EXIT ) emit_addr( <address> ) is what I'm after. It *looks* like CodeIndex holds the address where the code is being compiled to. I need two variables - one to hold the address of the label, and the other to hold the address of the goto to backpatch. In more advanced implementations, these would be coded as tables: int *goto_address = NULL; int *goto_backpatch = NULL; When 'label' executes, I can grab CodeIndex and store it, and backpatch any prior 'goto' statements: static void Label_statement() /* parse a label statement */ { TOKEN label; /* read the label */ token( label ); if (label.id != VARIABLE) { CompileErr("a label name is expected here"); } /* address already defined? */ if (goto_address != NULL) { CompileErr("label defined multiple times"); } /* set address */ goto_address = CodeIndex; /* backpatch prior statement? */ if (goto_backpatch != NULL) { backpatch( goto_backpatch, goto_address ); } } When 'goto' executes, if the label has been defined, it compiles: emit_op( EXIT ) emit_addr( goto_address ) Otherwise, it compiles: emit_op( EXIT ) emit_addr( NULL ) and stores the address that NULL is stored into goto_address to backpatch later: static void Goto_statement() /* parse a goto statement */ { TOKEN label; /* read the label */ token( label ); if (label.id != VARIABLE ) { CompileErr("a label name is expected here"); } /* emit an EXIT */ emit_op( EXIT ); /* does the label exist? */ if (goto_address) { emit_addr( goto_address ); } else { /* store address to backpatch*/ goto_backpatch = CodeIndex; /* put in dummy address */ emit_addr( NULL ); } } Unfortunately, this crashes and burns, so I'm getting *something* wrong. Anyone want to help me puzzle this one out? Thanks! -- David Cuny